为什么 Visual Studio 无法在模板 class 中选择正确的构造函数?

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

当我实例化模板 class 时,

Visual Studio 没有看到正确的构造函数。我哪里弄错了?

我已经尝试制作 copy/move 个构造函数 explicit/deleted。没用。

#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 C2664:  'connection<T>::connection(connection<T> &&)': cannot convert argument 1 from 'node<int, connection> *' to 'node<T, connection<T>> *'

好像是VS的bug。 VS 似乎将 injected class name connection 视为等同于 connection<T> 的类型名称,但应将其视为 class 模板本身的模板名称,即 [= node<T, connection>* n;connection(node<T, connection>* n)中的11=],因为node的第二个模板参数是模板模板参数。

(强调我的)

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:您的代码在 clang 下编译得很好。

PS:在bool operator<(const connection& b) const中被当作connection<T>处理。

在 class 模板的范围内,模板的名称实际上是 "injected class name",它的作用类似于 class 成员,可以用作模板名称或类型名称,表示使用的专业化。 ([temp.local]/1)

所以当这个名字被用作模板参数时,它可能意味着任何一个,因此编译器需要检查相应的模板参数是类型还是模板。 g++ 和 clang++ 按原样接受您的代码。但是 MSVC 有一个错误,它经常(但不总是)假定用作模板参数的注入 class 名称是 class 类型,即使唯一相关的模板参数是模板模板参数也是如此。 (原代码上的三个编译器:https://godbolt.org/z/xrJSPB

要解决此问题,当您指的是模板自身范围内的模板名称时,您可以使用限定名称,例如 ::connection

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

(所有三个编译器都接受这个:https://godbolt.org/z/st7liP