mrs*_*spl 5 c++ constructor overloading perfect-forwarding c++11
我有两个类,A和B,B从A派生.A有多个构造函数(下例中为2).B有一个额外的成员要初始化(它有一个默认的初始化程序).
我怎样才能实现B可以使用A的构造函数之一构造,而不必手动重写B中A的所有构造函数重载?
(在下面的例子中,我否则必须为B提供了四个构造函数:B():A(){},B(string s):A(s){},B(int b):A(),p(b){},B(string s, int b):A(s),p(b){},而不是只有两个,忽略的默认参数的可能性至少当).
我的方法是完美转发,但是,以下方案会导致错误:
#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);
}
Run Code Online (Sandbox Code Playgroud)
对于b2,海湾合作委员会抱怨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
Run Code Online (Sandbox Code Playgroud)
"foobar"是一个const char (&) [7].因此Args比a更好const std::string&
因此,选择了这个重载:
template<typename... Args>
B(Args&&... args) : A(std::forward<Args>(args)...) {}
Run Code Online (Sandbox Code Playgroud)
这里Args是const char (&) [7]
所以它变成:
B(const char (&&args_0) [7], int&& args_1)
Run Code Online (Sandbox Code Playgroud)
转发给A2的参数构造函数...它不存在.
想要的行为是,如果你构造一个带有适用于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);
}
Run Code Online (Sandbox Code Playgroud)
在看到A没有匹配的构造函数之后,为什么不返回并继续寻找可能匹配的其他B构造函数?有技术原因吗?
简而言之(并且非常简单地说),当发生重载解析时,编译器会执行以下操作:
展开所有可能与给定参数匹配的模板化重载.将它们添加到列表中(权重指示到达此处所涉及的专业化级别).
将任何具体的重载添加到列表中,可以通过合法地将转换运算符应用到参数,并使用权重指示将提供的参数转换为函数参数类型所需的转换次数.
通过提升"工作"或重量对列表进行排序.
选择一个需要最少工作的人.如果有一个最好的领带,错误.
编译器得到了一个.这不是递归搜索.
我向我们中的纯粹主义者道歉,他们会发现这种幼稚的解释令人反感:-)
从类继承构造函数A:
struct B : A
{
using A::A;
// ~~~~~~~~~^
B(const std::string& a, int b) : A(a), p(b) {}
int p = 0;
};
Run Code Online (Sandbox Code Playgroud)
使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;
};
Run Code Online (Sandbox Code Playgroud)