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 )