参考在转发时丢失(并且没有自动转换来拯救我)——为什么?
Reference lost on forwarding (and no auto-conversion to save me) - why?
我正在编写一个工厂,用于使用它们的名称生成基 class 的子classes 的实例,并将这个(模板化的)工厂与我的 class Foo
。不用管整个代码,但本质上,工厂有一个从字符串到创建实例的函数的映射;模板参数控制这些函数采用哪些参数。在我的例子中,Foo
的构造函数和 foo 的任何子 class 都采用 const Bar&
,这就是构造函数参数的可变参数模板的组成部分。
我有:
template<typename SubclassKey, typename T, typename... ConstructionArgs>
class Factory {
public:
using Instantiator = T* (*)(ConstructionArgs&&...);
private:
template<typename U>
static T* createInstance(ConstructionArgs&&... args)
{
return new U(std::forward<ConstructionArgs>(args)...);
}
using Instantiators = std::unordered_map<SubclassKey,Instantiator>;
Instantiators subclassInstantiators;
public:
template<typename U>
void registerSubclass(const SubclassKey& subclass_id)
{
static_assert(std::is_base_of<T, U>::value,
"This factory cannot register a class which is is not actually "
"derived from the factory's associated class");
auto it = subclassInstantiators.find(subclass_id);
if (it != subclassInstantiators.end()) {
throw std::logic_error("Repeat registration of the same subclass in this factory.");
}
subclassInstantiators.emplace(subclass_id, &createInstance<U>);
}
};
应大众需求,这里还有...
class Foo {
using FooFactory = Factory<std::string, Foo, const Bar&>;
private:
static FooFactory& getTestFactory() {
static FooFactory kernel_test_factory;
return kernel_test_factory;
}
//...
public:
//...
template <typename U>
static void registerInFactory(const std::string& name_of_u) {
Foo::getTestFactory().registerSubclass<U>(name_of_u);
}
Bar bar;
Foo(const Bar& bar_) : bar(bar_) { };
virtual ~Foo() {}
// ...
};
class NiceFoo : public Foo {
// no ctors and dtors
};
不幸的是,由于某种原因,当我打电话给
我收到关于 ctor 期望 const Bar&
的投诉,而我应该在 createInstance
中提供的参数列表实际上是 const Bar
.
问题:
- 为什么引用是"disappearing"?
- 我是不是做错了什么?我应该以不同的方式处理这个问题吗?
GCC 错误输出:
/home/joeuser/myproj/../util/Factory.h(36): error: no instance of constructor "NiceFoo::NiceFoo" matches the argument list
argument types are: (const Bar)
detected during:
instantiation of "T *util::Factory<SubclassKey, T, ConstructionArgs...>::createInstance<U>(ConstructionArgs &&...) [with SubclassKey=std::string, T=Foo, ConstructionArgs=<const Bar &>, U=NiceFoo]"
(59): here
instantiation of "void util::Factory<SubclassKey, T, ConstructionArgs...>::registerSubclass<U>(const SubclassKey &) [with SubclassKey=std::string, T=Foo, ConstructionArgs=<const Bar &>, U=NiceFoo]"
/home/joeuser/myproj/../Foo.h(79): here
instantiation of "void Foo::registerInFactory<U>(const std::string &) [with U=NiceFoo]"
/home/joeuser/myproj/NiceFoo.cpp(122): here
您的代码中有两个问题:直接问题和基本问题。
最直接的一个是NiceFoo
没有声明任何构造函数。您声称 "it inherits Foo
's constructors," 但您显示的代码并非如此。继承构造函数将通过以下方式实现:
class NiceFoo : public Foo {
public:
using Foo::Foo;
}
没有 using
声明,您只有一个 class 没有声明构造函数(它将默认生成复制、移动和无参数的构造函数,但没有新的构造函数)。
你的设计还有一个根本性的问题:你试图在完美转发中使用class模板的模板参数。那行不通;完美转发依赖于模板参数推导,这只发生在函数模板中。
换句话说,ConstructionArgs&&...
不会创建转发引用,而是创建普通的旧右值引用。在您提供的具体案例中,这并不重要,因为 ConstructionArgs
是 const Bar &
并且引用折叠可以解决问题。但是,如果您在构造函数参数中包含值类型(而不是引用类型),createInstance
将一直使用普通右值引用,并且不可能从左值进行初始化。
正确的解决方案是让 ConstructionArgs
镜像 完全 T
的构造函数所期望的——换句话说,删除 &&
从到处使用 ConstructionArgs
,并在 createInstance
中使用 std::move
而不是 std::forward
。转念一想,std::forward
应该可以留下来做正确的事;基本上等同于左值引用的 no-op,值和右值引用的 std::move
。
我正在编写一个工厂,用于使用它们的名称生成基 class 的子classes 的实例,并将这个(模板化的)工厂与我的 class Foo
。不用管整个代码,但本质上,工厂有一个从字符串到创建实例的函数的映射;模板参数控制这些函数采用哪些参数。在我的例子中,Foo
的构造函数和 foo 的任何子 class 都采用 const Bar&
,这就是构造函数参数的可变参数模板的组成部分。
我有:
template<typename SubclassKey, typename T, typename... ConstructionArgs>
class Factory {
public:
using Instantiator = T* (*)(ConstructionArgs&&...);
private:
template<typename U>
static T* createInstance(ConstructionArgs&&... args)
{
return new U(std::forward<ConstructionArgs>(args)...);
}
using Instantiators = std::unordered_map<SubclassKey,Instantiator>;
Instantiators subclassInstantiators;
public:
template<typename U>
void registerSubclass(const SubclassKey& subclass_id)
{
static_assert(std::is_base_of<T, U>::value,
"This factory cannot register a class which is is not actually "
"derived from the factory's associated class");
auto it = subclassInstantiators.find(subclass_id);
if (it != subclassInstantiators.end()) {
throw std::logic_error("Repeat registration of the same subclass in this factory.");
}
subclassInstantiators.emplace(subclass_id, &createInstance<U>);
}
};
应大众需求,这里还有...
class Foo {
using FooFactory = Factory<std::string, Foo, const Bar&>;
private:
static FooFactory& getTestFactory() {
static FooFactory kernel_test_factory;
return kernel_test_factory;
}
//...
public:
//...
template <typename U>
static void registerInFactory(const std::string& name_of_u) {
Foo::getTestFactory().registerSubclass<U>(name_of_u);
}
Bar bar;
Foo(const Bar& bar_) : bar(bar_) { };
virtual ~Foo() {}
// ...
};
class NiceFoo : public Foo {
// no ctors and dtors
};
不幸的是,由于某种原因,当我打电话给
我收到关于 ctor 期望 const Bar&
的投诉,而我应该在 createInstance
中提供的参数列表实际上是 const Bar
.
问题:
- 为什么引用是"disappearing"?
- 我是不是做错了什么?我应该以不同的方式处理这个问题吗?
GCC 错误输出:
/home/joeuser/myproj/../util/Factory.h(36): error: no instance of constructor "NiceFoo::NiceFoo" matches the argument list
argument types are: (const Bar)
detected during:
instantiation of "T *util::Factory<SubclassKey, T, ConstructionArgs...>::createInstance<U>(ConstructionArgs &&...) [with SubclassKey=std::string, T=Foo, ConstructionArgs=<const Bar &>, U=NiceFoo]"
(59): here
instantiation of "void util::Factory<SubclassKey, T, ConstructionArgs...>::registerSubclass<U>(const SubclassKey &) [with SubclassKey=std::string, T=Foo, ConstructionArgs=<const Bar &>, U=NiceFoo]"
/home/joeuser/myproj/../Foo.h(79): here
instantiation of "void Foo::registerInFactory<U>(const std::string &) [with U=NiceFoo]"
/home/joeuser/myproj/NiceFoo.cpp(122): here
您的代码中有两个问题:直接问题和基本问题。
最直接的一个是NiceFoo
没有声明任何构造函数。您声称 "it inherits Foo
's constructors," 但您显示的代码并非如此。继承构造函数将通过以下方式实现:
class NiceFoo : public Foo {
public:
using Foo::Foo;
}
没有 using
声明,您只有一个 class 没有声明构造函数(它将默认生成复制、移动和无参数的构造函数,但没有新的构造函数)。
你的设计还有一个根本性的问题:你试图在完美转发中使用class模板的模板参数。那行不通;完美转发依赖于模板参数推导,这只发生在函数模板中。
换句话说,ConstructionArgs&&...
不会创建转发引用,而是创建普通的旧右值引用。在您提供的具体案例中,这并不重要,因为 ConstructionArgs
是 const Bar &
并且引用折叠可以解决问题。但是,如果您在构造函数参数中包含值类型(而不是引用类型),createInstance
将一直使用普通右值引用,并且不可能从左值进行初始化。
正确的解决方案是让 ConstructionArgs
镜像 完全 T
的构造函数所期望的——换句话说,删除 &&
从到处使用 ConstructionArgs
,并在 中使用 createInstance
std::move
而不是 std::forward
。转念一想,std::forward
应该可以留下来做正确的事;基本上等同于左值引用的 no-op,值和右值引用的 std::move
。