Why does Visual Studio fail to choose the right constructor in template class?
Asked Answered
C

2

6

Visual Studio doesn't see the right constructor when I instantiate the template class. Where did I make a mistake?

I've already tried to make copy/move constructors explicit/deleted. Doesn't help.

#include <set>

using namespace std;

template <class T, template<class> class ConnectionType>
struct node
{
    T value;
    node(const T& value) : value(value) {}

    set<ConnectionType<T>> connections;
};

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;
    }
};

int main()
{
    node<int, connection> a(0);
    connection<int> c(&a); // ERROR HERE

    return 0;
}

Error:

error C2664:  'connection<T>::connection(connection<T> &&)': cannot convert argument 1 from 'node<int, connection> *' to 'node<T, connection<T>> *'
Celestina answered 1/8, 2019 at 13:53 Comment(6)
Shouldn't it be node<int, connection<int>> a(0); instead?Epos
@Epos no, ConnectionType is a template template parameterHafler
This is an MSVC bug. Not reproducible with gcc (trunk) or clang (trunk). /permissive- doesn't help :(Overcapitalize
this does compile well on an online compiler.Ascertain
it has something to do with connection being a short way of writing connection<T> inside the definition of connection<T>, though I have no clue how to fix itHafler
@formerlyknownas_463035818 "injected-class-name" might be the name you're looking for.Overcapitalize
K
3

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 )

Kanya answered 1/8, 2019 at 14:6 Comment(0)
P
5

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.

Pinup answered 1/8, 2019 at 14:4 Comment(0)
K
3

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 )

Kanya answered 1/8, 2019 at 14:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.