Given a template member function (... a templated constructor in a templated class, as it happens) taking a variadic universal reference, and another candidate function (copy constructor, in my case), both GCC and clang select the universal reference template in overload resolution when copying an object. Since two entirely different compilers behave identically, I will assume that this is indeed the "correct thing".
Example code at https://godbolt.org/g/mip3Fi.
Thus, when a copy is made, there are two viable candidate functions with the same number of parameters, both with a perfect match (after reference collapsing for the template version) of which one is a candidate function template specialization, and the other is not. And of course, of which calling one is "clearly desired" whereas calling the other is not.
My understanding of overload resolution is that among the set of candidate functions, the best viable candidate is to be selected, and in case a template and a non-template candidate which are equivalently viable compete, the non-template candidate is chosen. That would be the copy constructor.
Obviously, the compiler (and presumably the standard?) thinks differently. What's the rationale for this rather non-obvious exception?
Bonus question:
How do I prevent that from happening, i.e. how do I tell the compiler that I really want to invoke the copy constructor when making a copy?
Something like enable_if_t<!is_same<container<T> const& blahblah...> blah>
elimiates the bad candidate, and copies work as-intended, but the approach is not overly helpful because it will fail to compile the moment you call the variadic template with more than one argument (which is its original purpose!).
Writing an initializer_list
aware function instead would work around the problem, but that will turn a perfectly forwarded emplace into a copy and move, which is not precisely nice either.
EDIT:
(the above linked code, as requested)
#include <cstdio>
template<typename T> struct container
{
container() { puts("default"); }
container(container const&) { puts("copy"); /* blah */ }
template<typename... U> container(U&&... args) { puts("template"); /* new T[sizeof...(args)]{std::forward<T>(args)...}; blah blah */ }
};
int main()
{
container<int> f;
container<int> g{1,2,3};
container<int> h(f); // <--- copy constructor most viable form?!
return 0;
}