C 构造函数:完美的转发和重载

新手上路,请多包涵

我有两个类,A 和 B,B 派生自 A。A 有多个构造函数(下例中为 2 个)。 B 有一个额外的成员要初始化(它有一个默认的初始化程序)。

我怎样才能实现 B 可以使用 A 的构造函数之一来构造,而不必手动重写 B 中 A 的所有构造函数重载?

(In the example below, I would otherwise have to provide four constructors for B: B():A(){} , B(string s):A(s){} , B(int b):A(),p(b){} , B(string s, int b):A(s),p(b){} , instead只有两个,至少在忽略默认参数的可能性时)。

我的方法是完美转发,但是,以下情况会导致错误:

 #include <utility>
#include <string>

struct A {
    A(const std::string& a) : name(a) {}
    A(){}
    virtual ~A(){}

    std::string name;
};

struct B : public A {
    template<typename... Args>
    B(Args&&... args) : A(std::forward<Args>(args)...) {}

    B(const std::string& a, int b) : A(a), p(b) {}

    int p = 0;
};

int main()
{
    B b1("foo");
    B b2("foobar", 1);
}

对于 b2,GCC 抱怨 no matching function for call to 'A::A(const char [5], int) 。显然它正在尝试调用完美的转发构造函数,这显然不应该工作,而不是 B 的第二个构造函数。

为什么没有看到编译器第二个构造函数而是调用它呢?编译器找不到 B 的正确构造函数是否存在技术原因?我该如何解决这种行为?

确切的错误信息:

 main.cpp: In instantiation of 'B::B(Args&& ...) [with Args = {const char (&)[5], int}]':
main.cpp:26:19:   required from here
main.cpp:15:54: error: no matching function for call to 'A::A(const char [5], int)'
     B(Args&&... args) : A(std::forward<Args>(args)...) {}
                                                      ^
main.cpp:6:5: note: candidate: A::A()
     A(){}
     ^
main.cpp:6:5: note:   candidate expects 0 arguments, 2 provided
main.cpp:5:5: note: candidate: A::A(const string&)
     A(const std::string& a) : name(a) {}
     ^
main.cpp:5:5: note:   candidate expects 1 argument, 2 provided
main.cpp:4:8: note: candidate: A::A(const A&)
 struct A {
        ^
main.cpp:4:8: note:   candidate expects 1 argument, 2 provided

原文由 mrspl 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 776
2 个回答

"foobar"const char (&) [7] 。因此 Argsconst std::string& 更好的匹配

因此,选择了这个重载:

 template<typename... Args>
B(Args&&... args) : A(std::forward<Args>(args)...) {}

其中 Argsconst char (&) [7]

所以它变成:

 B(const char (&&args_0) [7], int&& args_1)

它被转发到 A 的 2 参数构造函数……它不存在。

想要的行为是,如果您使用适用于 A 的构造函数构造 B,则调用 B 的“…Args 构造函数”,否则调用 B 的另一个构造函数,否则失败并显示“找不到 B 的合适构造函数” ”。

像这样的东西……

 #include <utility>
#include <string>

struct A {
    A(std::string a) : name(std::move(a)) {}
    A(){}
    virtual ~A(){}

    std::string name;
};

template<class...T> struct can_construct_A
{
    template<class...Args> static auto test(Args&&...args)
    -> decltype(A(std::declval<Args>()...), void(), std::true_type());

    template<class...Args> static auto test(...) -> std::false_type;

    using type = decltype(test(std::declval<T>()...));
    static constexpr bool value = decltype(test(std::declval<T>()...))::value;
};

struct B : public A {

    template<class...Args>
    B(std::true_type a_constructable, Args&&...args)
    : A(std::forward<Args>(args)...)
    {}

    template<class Arg1, class Arg2>
    B(std::false_type a_constructable, Arg1&& a1, Arg2&& a2)
    : A(std::forward<Arg1>(a1))
    , p(std::forward<Arg2>(a2))
    {
    }

    template<typename... Args>
    B(Args&&... args)
    : B(typename can_construct_A<Args&&...>::type()
        , std::forward<Args>(args)...) {}

    int p = 0;
};

int main()
{
    B b1("foo");
    B b2("foobar", 1);
}

看到 A 没有匹配的构造函数后,为什么不回去继续寻找可能匹配的 B 的其他构造函数呢?有技术原因吗?

简而言之(并且说得很简单),当发生重载决议时,编译器会执行以下操作:

  1. 展开所有可能匹配给定参数的模板化重载。将它们添加到列表中(权重表示到达那里所涉及的专业化水平)。

  2. 将任何具体的重载添加到列表中,这些重载可以通过合法地将转换运算符应用于参数来实现,权重指示需要多少次转换才能将提供的参数转换为函数参数类型。

  3. 按升序“工作”或权重对列表进行排序。

  4. 选择需要最少工作的那个。如果有一个最适合的平局,那就错了。

编译器对此进行了尝试。这不是递归搜索。

我提前向我们中间的纯粹主义者道歉,他们会觉得这种幼稚的解释令人反感:-)

原文由 Richard Hodges 发布,翻译遵循 CC BY-SA 3.0 许可协议

选项1

从类 A 继承构造函数:

 struct B : A
{
    using A::A;
//  ~~~~~~~~~^

    B(const std::string& a, int b) : A(a), p(b) {}

    int p = 0;
};

选项 #2

使 B 的可变参数构造函数具有 SFINAE 能力:

 #include <utility>

struct B : A
{
    template <typename... Args, typename = decltype(A(std::declval<Args>()...))>
    //                                     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^
    B(Args&&... args) : A(std::forward<Args>(args)...) {}

    B(const std::string& a, int b) : A(a), p(b) {}

    B(B& b) : B(static_cast<const B&>(b)) {}
    B(const B& b) : A(b) {}

    int p = 0;
};

原文由 Piotr Skotnicki 发布,翻译遵循 CC BY-SA 3.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
logo
Stack Overflow 翻译
子站问答
访问
宣传栏