C++ 模板代码中的类型不完整

C++ 模板代码中的类型不完整,c++,templates,c++11,incomplete-type,C++,Templates,C++11,Incomplete Type,假设我们有两种类型(完全和不完全): 我们还有模板代码: #include <type_traits> template <typename = X(T)> struct Test : std::false_type {}; template <> struct Test<T> : std::true_type {}; 下面您可以看到不同的编译器如何处理这些代码: 叮当声3.4 错误:即使在类型不完整的模板类声明中(对于decltype(T

假设我们有两种类型(完全和不完全):

我们还有模板代码:

#include <type_traits>

template <typename = X(T)>
struct Test : std::false_type {};

template <>
struct Test<T> : std::true_type {};
下面您可以看到不同的编译器如何处理这些代码:


叮当声3.4

  • 错误:即使在类型不完整的模板类声明中(对于
    decltype(T())
    decltype(T{})
    ,但对于简单的
    T
    ),如果不在代码中使用
    Test::value
    ,也会使用不完整的类型“不完整类型”

  • 错误:类模板“Test”的模板参数太少。


  • g++4.8.1


    vc++18.00.21005.1

  • 错误C2514:“不完整类型”:类没有构造函数

  • 错误C2440:'':无法从“初始值设定项列表”转换为“不完整类型”源或目标的类型不完整



  • 什么编译器的行为符合标准?请注意像
    std::cout这样的简单字符串,我相信在这种情况下,Clang和MSVC的行为符合标准。我认为GCC在这方面走了一条捷径

    让我们先把一些事实摆在桌面上。
    decltype
    表达式的操作数被称为未计算的操作数,由于它们最终从未被计算过,因此处理方式有所不同

    特别是,关于类型是否完整的要求更少。基本上,如果有任何临时对象(作为表达式中涉及的函数或运算符中的参数或返回值),则不需要完整(请参见第5.2.2/11节和第7.1.6.2/5节)。但这只是取消了通常的“不能声明不完整类型的对象”限制,但没有取消对不完整类型的其他限制,即“不能调用不完整类型的成员函数”。那就是踢球的人

    表达式
    decltype(T())
    decltype(T{})
    ,其中
    T
    不完整,必须查找类型
    T
    的构造函数,因为它是该类的(特殊)成员函数。这只是一个事实,它是一个构造函数调用,创建了一点模糊性(即,它只是创建了一个临时对象?还是调用了一个构造函数?)。如果是任何其他成员的职能,就不会有辩论。幸运的是,该标准确实解决了这一争论:

    12.2/1

    即使临时对象的创建未计算(第 5) 或以其他方式避免(12.8),所有语义限制应 被尊重,就好像临时对象是在以后创建的一样 摧毁。[注意:即使没有调用析构函数或 复制/移动构造函数,所有语义限制,例如 可访问性(第11条)以及是否删除该功能 (8.4.3),但在特殊情况下 用作decltype说明符(5.2.2)的操作数的函数调用,否 引入了临时性,因此上述内容不适用于 任何此类函数调用的prvalue.-结束注释]

    最后一句可能有点混乱,但这只适用于函数调用的返回值。换句话说,如果你有
    tf()函数,并声明
    decltype(f())
    ,则
    T
    不需要完整,也不需要对其是否有可用和可访问的构造函数/析构函数进行语义检查

    事实上,这整个问题正是为什么存在
    std::declval
    实用程序的原因,因为当您不能使用
    decltype(T())
    时,您可以只使用
    decltype(std::declval())
    ,而
    declval
    只不过是一个返回类型为
    T
    的PR值的(伪)函数。但是,当然,
    declval
    旨在用于不那么琐碎的情况,例如
    decltype(f(std::declval())
    其中
    f
    将是一个接受类型为
    T
    的对象的函数。并且
    declval
    不要求类型完整(见第20.2.4节)。这基本上就是解决整个问题的方法

    因此,就GCC的行为而言,我认为它需要一条捷径,因为它试图找出
    T()
    T{}
    的类型。我认为,只要GCC发现
    T
    引用了一个类型名(不是函数名),它就会推断这是一个构造函数调用,因此,无论查找结果如何,作为被调用的实际构造函数,返回类型都将是
    T
    (严格来说,构造函数没有返回类型,但您理解我的意思)。这里的要点是,这可能是未赋值表达式中有用(更快)的捷径。但据我所知,这不是符合标准的行为

    如果GCC允许
    CompleteType
    的构造函数被删除或私有,那么这也与上述引用的标准段落直接矛盾。编译器需要在这种情况下强制执行所有语义限制,即使表达式未被计算


    请注意,像
    std::cout这样的简单字符串“X(T)可以是T、decltype(T())或decltype(T{})”是什么意思?
    X(T)
    X(T)
    。它怎么可能是
    T
    decltype(T())
    decltype(T{})
    ?所有这些类型都是不同的,永远不会是相同的函数类型!@johanneschaub litb想象
    X(T)
    是宏。如果你不想让它成为一个糟糕的问题,那应该写在你的问题中。我很难复制代码,但我很好奇如果你做
    struct CompleteType会发生什么{CompleteType()=delete;};
    (如果有人关心,使用g++时,结果仍然是正确的)6年后,gcc 9.3仍然存在错误()。它接受代码,这
    #include <type_traits>
    
    template <typename = X(T)>
    struct Test : std::false_type {};
    
    template <>
    struct Test<T> : std::true_type {};
    
    std::cout << std::boolalpha << Test<>::value << std::endl;
    
    X(T) \ T       CompleteType  IncompleteType
    T              true          true      
    decltype(T())  true          --- (1, 2)
    decltype(T{})  true          --- (1, 2)
    
    X(T) \ T       CompleteType  IncompleteType
    T              true          true      
    decltype(T())  true          true      
    decltype(T{})  true          true      
    
    X(T) \ T       CompleteType  IncompleteType
    T              true          true      
    decltype(T())  true          --- (1)   
    decltype(T{})  true          --- (2)