The following code does not compile:
#include
#include
struct Foo
{
Foo() { std::cout << \"Foo()\" << std::
This call:
Bar<Foo> bar2{bar1};
has two candidates in its overload set:
Bar(const Bar&);
Bar(Bar&); // Args... = {Bar&}
One of the ways to determine if one conversion sequence is better than the other is, from [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 ]
The forwarding reference variadic constructor is a better match because its reference binding (Bar&
) is less cv-qualified than the copy constructor's reference binding (const Bar&
).
As far as solutions, you could simply exclude from the candidate set anytime Args...
is something that you should call the copy or move constructor with 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)
If Args...
is one of Bar
, Bar&
, Bar&&
, const Bar&
, then typelist<decay_t<Args>...>
will be typelist<Bar>
- and that's a case we want to exclude. Any other set of Args...
will be allowed just fine.
While I agree that it's counter-intuitive, the reason is that your copy constructor takes a const Bar&
but bar1
is not const.
http://coliru.stacked-crooked.com/a/2622b4871d6407da
Since the universal reference can bind anything it is chosen over the more restrictive constructor with the const requirement.
Another way to avoid the variadic constructor being selected is to supply all forms of the Bar
constructor.
It's a little more work, but avoids the complexity of enable_if, if that's important to you:
#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};
}