模板代码中的类型不完整

Con*_*tor 14 c++ templates incomplete-type c++11

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

struct CompleteType{};

struct IncompleteType;
Run Code Online (Sandbox Code Playgroud)

我们还有模板代码:

#include <type_traits>

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

template <>
struct Test<T> : std::true_type {};
Run Code Online (Sandbox Code Playgroud)

T可以是CompleteTypeIncompleteType在这里X(T)可以T,decltype(T())或者decltype(T{})(假设X(T)是一个宏).

此代码以下列方式使用:

std::cout << std::boolalpha << Test<>::value << std::endl;
Run Code Online (Sandbox Code Playgroud)

下面你可以看到不同的编译器如何处理这样的代码:


铿锵3.4

X(T) \ T       CompleteType  IncompleteType
T              true          true      
decltype(T())  true          --- (1, 2)
decltype(T{})  true          --- (1, 2)
Run Code Online (Sandbox Code Playgroud)
  1. error: invalid use of incomplete type 'IncompleteType'甚至在模板类声明中给出了不完整类型(对于decltype(T())decltype(T{}),但不是为了简单T)而不Test<>::value在代码中使用.

  2. error: too few template arguments for class template 'Test'


g ++ 4.8.1

X(T) \ T       CompleteType  IncompleteType
T              true          true      
decltype(T())  true          true      
decltype(T{})  true          true      
Run Code Online (Sandbox Code Playgroud)

vc ++ 18.00.21005.1

X(T) \ T       CompleteType  IncompleteType
T              true          true      
decltype(T())  true          --- (1)   
decltype(T{})  true          --- (2)   
Run Code Online (Sandbox Code Playgroud)
  1. error C2514: 'IncompleteType' : class has no constructors

  2. error C2440: '<function-style-cast>' : cannot convert from 'initializer-list' to 'IncompleteType' Source or target has incomplete type


什么编译器按照标准行事?请注意,std::cout << typeid(X(IncompleteType)).name() << std::endl;对于所有变体X(除了vc ++ X(T) == T),所有编译器都不会编译简单的字符串.

Mik*_*son 11

我相信Clang和MSVC的行为在这种情况下与标准一致.我认为海湾合作委员会在这里采取了一些捷径.

我们先把一些事实放在桌面上.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-specifier(5.2.2)的操作数的函数调用的特殊情况下,不引入临时,因此前述内容不适用于任何此类函数调用的prvalue. - 结束说明]

最后一句可能有点令人困惑,但这只适用于函数调用的返回值.换句话说,如果你有T f();函数,并且你声明了decltype(f()),那么T就不需要完成或者对是否有可用的构造函数/析构函数进行任何语义检查.

事实上,整个问题正是为什么有一个std::declval实用程序,因为当你不能使用时decltype(T()),你可以只使用decltype(std::declval<T>()),而且declval只不过是一个返回类型的prvalue的(假的)函数T.不过,当然,declval有意识地在更少琐碎的情况下,如要使用decltype( f( std::declval<T>() ) )其中f将考虑类型的对象的函数T.并且declval不要求类型完整(参见第20.2.4节).这基本上就是解决这个问题的方法.

所以,只要GCC的行为来看,我认为,它需要一条捷径,因为它试图弄清楚的类型是什么T()或者T{}是.我认为,只要GCC发现T引用类型名称(而不是函数名称),就会推断出这是一个构造函数调用,因此,无论查找的内容是什么,实际的构造函数都被调用,返回类型将是T(严格来说,构造函数没有返回类型,但你明白我的意思).这里的要点是,在未评估的表达中,这可能是一个有用的(更快的)捷径.但据我所知,这不是符合标准的行为.

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

请注意,std::cout << typeid(X(IncompleteType)).name() << std::endl;对于X的所有变体,所有编译器都不会编译简单的字符串(vc ++和X(T)== T除外).

这是预期的(除了MSVC和X(T)== T).在typeidsizeof运营商类似,decltype在这个意义上,它们的操作数是未计算的,但是,它们都有所产生的表达式的类型必须是一个完整类型的附加要求.可以想象,编译器可以解析typeid不完整类型(或至少使用部分类型信息),但标准需要完整类型,以便编译器不必执行此操作.我想这就是MSVC正在做的事情.

因此,在这种情况下,T()T{}案例失败的原因与decltype(正如我刚才解释的)相同,并且X(T) == T案例失败,因为typeid需要一个完整的类型(但MSVC设法解除了这个要求).在海湾合作委员会上,由于typeid要求所有X(T)案件都有完整的类型,因此失败(即,短线GCC采取不影响结果的情况sizeoftypeid).

所以,总而言之,我认为Clang是三者中最符合标准的(不采取捷径或扩展).