参考在转发时丢失(并且没有自动转换来拯救我)——为什么?

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.

问题:

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&&... 不会创建转发引用,而是创建普通的旧右值引用。在您提供的具体案例中,这并不重要,因为 ConstructionArgsconst Bar & 并且引用折叠可以解决问题。但是,如果您在构造函数参数中包含值类型(而不是引用类型),createInstance 将一直使用普通右值引用,并且不可能从左值进行初始化。

正确的解决方案是让 ConstructionArgs 镜像 完全 T 的构造函数所期望的——换句话说,删除 && 从到处使用 ConstructionArgs,并在 createInstance 中使用 std::move 而不是 std::forward。转念一想,std::forward 应该可以留下来做正确的事;基本上等同于左值引用的 no-op,值和右值引用的 std::move