Reputation: 391
The following code does not compile:
#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};
}
Compiler error suggest to me that compiler was trying to use variadic template constructor instead of copy constructor:
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)...)
Why compiler does that and how to fix it?
Upvotes: 11
Views: 1272
Reputation: 1
The "std-way" to solve this issue is to put a parameter of std::in_place_t first. That way you have a clear type to force the compiler to use the templated constructor when you want and to not let it match when you don't want. You could check the way it is done here https://en.cppreference.com/w/cpp/utility/optional/optional.
Upvotes: 0
Reputation: 69902
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};
}
Upvotes: 0
Reputation: 303337
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.
Upvotes: 12
Reputation: 2234
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.
Upvotes: 6