Why does Visual Studio fail to choose the right constructor in template class?
It seems to be VS's bug. VS seems treating the injected class name connection
as the type-name equivalent to connection<T>
, but it should be treated as the template-name of the class template itself, i.e. connection
in node<T, connection>* n;
and connection(node<T, connection>* n)
, because the 2nd template parameter of node
is a template template parameter.
(emphasis mine)
In the following cases, the injected-class-name is treated as a template-name of the class template itself:
- it is followed by <
- it is used as a template argument that corresponds to a template template parameter
- it is the final identifier in the elaborated class specifier of a friend class template declaration.
Otherwise, it is treated as a type-name, and is equivalent to the template-name followed by the template-parameters of the class template enclosed in <>.
template <template <class, class> class> struct A; template<class T1, class T2> struct X { X<T1, T2>* p; // OK, X is treated as a template-name using a = A<X>; // OK, X is treated as a template-name template<class U1, class U2> friend class X; // OK, X is treated as a template-name X* q; // OK, X is treated as a type-name, equivalent to X<T1, T2> };
PS: Your code compiles well with clang.
PS: It's treated as connection<T>
in bool operator<(const connection& b) const
.
Within a class template's scope, the name of the template actually is the "injected class name" which acts like a class member, and can be used as either the template name or as a type name, meaning the specialization in use. ([temp.local]/1)
So when this name is used as a template argument, it could mean either, and so a compiler needs to check for whether the corresponding template parameter is a type or a template. g++ and clang++ accept your code as is. But MSVC has a bug where it often (but not always) assumes the injected class name used as a template argument is a class type, even when the only relevant template parameter is a template template parameter. (The three compilers on the original code: https://godbolt.org/z/xrJSPB )
To work around this, you can use a qualified name like ::connection
when you mean the name of the template from inside its own scope.
template <class T>
struct connection
{
node<T, ::connection>* n;
connection(node<T, ::connection>* n) :
n(n) {}
bool operator<(const connection& b) const
{
return n < b.n;
}
};
(All three compilers accept this: https://godbolt.org/z/st7liP )