为什么可变参数模板构造函数比复制构造函数更匹配?
Why variadic template constructor matches better than copy constructor?
以下代码无法编译:
#include <iostream>
#include <utility>
struct Foo
{
Foo() { std::cout << "Foo()" << std::endl; }
Foo(int) { std::cout << "Foo(int)" << std::endl; }
};
template <typename T>
struct Bar
{
Foo foo;
Bar(const Bar&) { std::cout << "Bar(const Bar&)" << std::endl; }
template <typename... Args>
Bar(Args&&... args) : foo(std::forward<Args>(args)...)
{
std::cout << "Bar(Args&&... args)" << std::endl;
}
};
int main()
{
Bar<Foo> bar1{};
Bar<Foo> bar2{bar1};
}
编译器错误提示我编译器试图使用可变模板构造函数而不是复制构造函数:
prog.cpp: In instantiation of 'Bar<T>::Bar(Args&& ...) [with Args = {Bar<Foo>&}; T = Foo]':
prog.cpp:27:20: required from here
prog.cpp:18:55: error: no matching function for call to 'Foo::Foo(Bar<Foo>&)'
Bar(Args&&... args) : foo(std::forward<Args>(args)...)
为什么编译器会这样做以及如何解决它?
虽然我同意这是违反直觉的,但原因是您的复制构造函数采用 const Bar&
但 bar1
不是 const。
http://coliru.stacked-crooked.com/a/2622b4871d6407da
由于通用引用可以绑定任何东西,因此它选择了具有 const 要求的限制性更强的构造函数。
这次通话:
Bar<Foo> bar2{bar1};
其超载集中有两个候选项:
Bar(const Bar&);
Bar(Bar&); // Args... = {Bar&}
确定一个转换序列是否优于另一个的方法之一是,来自 [over.ics.rank]:
Standard conversion sequence S1 is a better conversion sequence than standard conversion sequence
S2 if
— [...]
— S1 and S2 are reference bindings (8.5.3), and the types to which the references refer are the same
type except for top-level cv-qualifiers, and the type to which the reference initialized by S2 refers
is more cv-qualified than the type to which the reference initialized by S1 refers. [ Example:
int f(const int &);
int f(int &);
int g(const int &);
int g(int);
int i;
int j = f(i); // calls f(int &)
int k = g(i); // ambiguous
—end example ]
转发引用可变参数构造函数是一个更好的匹配,因为它的引用绑定(Bar&
)比复制构造函数的引用绑定([=15]更少cv限定=]).
就解决方案而言,您可以随时从候选集中排除 Args...
是您应该使用 SFINAE 调用复制或移动构造函数的东西:
template <typename... > struct typelist;
template <typename... Args,
typename = std::enable_if_t<
!std::is_same<typelist<Bar>,
typelist<std::decay_t<Args>...>>::value
>>
Bar(Args&&... args)
如果Args...
是Bar
、Bar&
、Bar&&
、const Bar&
之一,则typelist<decay_t<Args>...>
将是typelist<Bar>
- 这是我们想要排除的情况。任何其他 Args...
组都可以。
另一种避免选择可变构造函数的方法是提供所有形式的 Bar
构造函数。
工作量稍大,但避免了 enable_if 的复杂性,如果这对您很重要:
#include <iostream>
#include <utility>
struct Foo
{
Foo() { std::cout << "Foo()" << std::endl; }
Foo(int) { std::cout << "Foo(int)" << std::endl; }
};
template <typename T>
struct Bar
{
Foo foo;
Bar(const Bar&) { std::cout << "Bar(const Bar&)" << std::endl; }
Bar(Bar&) { std::cout << "Bar(Bar&)" << std::endl; }
Bar(Bar&&) { std::cout << "Bar(Bar&&)" << std::endl; }
template <typename... Args>
Bar(Args&&... args) : foo(std::forward<Args>(args)...)
{
std::cout << "Bar(Args&&... args)" << std::endl;
}
};
int main()
{
Bar<Foo> bar1{};
Bar<Foo> bar2{bar1};
}
以下代码无法编译:
#include <iostream>
#include <utility>
struct Foo
{
Foo() { std::cout << "Foo()" << std::endl; }
Foo(int) { std::cout << "Foo(int)" << std::endl; }
};
template <typename T>
struct Bar
{
Foo foo;
Bar(const Bar&) { std::cout << "Bar(const Bar&)" << std::endl; }
template <typename... Args>
Bar(Args&&... args) : foo(std::forward<Args>(args)...)
{
std::cout << "Bar(Args&&... args)" << std::endl;
}
};
int main()
{
Bar<Foo> bar1{};
Bar<Foo> bar2{bar1};
}
编译器错误提示我编译器试图使用可变模板构造函数而不是复制构造函数:
prog.cpp: In instantiation of 'Bar<T>::Bar(Args&& ...) [with Args = {Bar<Foo>&}; T = Foo]':
prog.cpp:27:20: required from here
prog.cpp:18:55: error: no matching function for call to 'Foo::Foo(Bar<Foo>&)'
Bar(Args&&... args) : foo(std::forward<Args>(args)...)
为什么编译器会这样做以及如何解决它?
虽然我同意这是违反直觉的,但原因是您的复制构造函数采用 const Bar&
但 bar1
不是 const。
http://coliru.stacked-crooked.com/a/2622b4871d6407da
由于通用引用可以绑定任何东西,因此它选择了具有 const 要求的限制性更强的构造函数。
这次通话:
Bar<Foo> bar2{bar1};
其超载集中有两个候选项:
Bar(const Bar&);
Bar(Bar&); // Args... = {Bar&}
确定一个转换序列是否优于另一个的方法之一是,来自 [over.ics.rank]:
Standard conversion sequence S1 is a better conversion sequence than standard conversion sequence S2 if
— [...]
— S1 and S2 are reference bindings (8.5.3), and the types to which the references refer are the same type except for top-level cv-qualifiers, and the type to which the reference initialized by S2 refers is more cv-qualified than the type to which the reference initialized by S1 refers. [ Example:int f(const int &); int f(int &); int g(const int &); int g(int); int i; int j = f(i); // calls f(int &) int k = g(i); // ambiguous
—end example ]
转发引用可变参数构造函数是一个更好的匹配,因为它的引用绑定(Bar&
)比复制构造函数的引用绑定([=15]更少cv限定=]).
就解决方案而言,您可以随时从候选集中排除 Args...
是您应该使用 SFINAE 调用复制或移动构造函数的东西:
template <typename... > struct typelist;
template <typename... Args,
typename = std::enable_if_t<
!std::is_same<typelist<Bar>,
typelist<std::decay_t<Args>...>>::value
>>
Bar(Args&&... args)
如果Args...
是Bar
、Bar&
、Bar&&
、const Bar&
之一,则typelist<decay_t<Args>...>
将是typelist<Bar>
- 这是我们想要排除的情况。任何其他 Args...
组都可以。
另一种避免选择可变构造函数的方法是提供所有形式的 Bar
构造函数。
工作量稍大,但避免了 enable_if 的复杂性,如果这对您很重要:
#include <iostream>
#include <utility>
struct Foo
{
Foo() { std::cout << "Foo()" << std::endl; }
Foo(int) { std::cout << "Foo(int)" << std::endl; }
};
template <typename T>
struct Bar
{
Foo foo;
Bar(const Bar&) { std::cout << "Bar(const Bar&)" << std::endl; }
Bar(Bar&) { std::cout << "Bar(Bar&)" << std::endl; }
Bar(Bar&&) { std::cout << "Bar(Bar&&)" << std::endl; }
template <typename... Args>
Bar(Args&&... args) : foo(std::forward<Args>(args)...)
{
std::cout << "Bar(Args&&... args)" << std::endl;
}
};
int main()
{
Bar<Foo> bar1{};
Bar<Foo> bar2{bar1};
}