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可以是CompleteType或IncompleteType在这里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)
error: invalid use of incomplete type 'IncompleteType'甚至在模板类声明中给出了不完整类型(对于decltype(T())和decltype(T{}),但不是为了简单T)而不Test<>::value在代码中使用.
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)
error C2514: 'IncompleteType' : class has no constructors
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).在typeid和sizeof运营商类似,decltype在这个意义上,它们的操作数是未计算的,但是,它们都有所产生的表达式的类型必须是一个完整类型的附加要求.可以想象,编译器可以解析typeid不完整类型(或至少使用部分类型信息),但标准需要完整类型,以便编译器不必执行此操作.我想这就是MSVC正在做的事情.
因此,在这种情况下,T()和T{}案例失败的原因与decltype(正如我刚才解释的)相同,并且X(T) == T案例失败,因为typeid需要一个完整的类型(但MSVC设法解除了这个要求).在海湾合作委员会上,由于typeid要求所有X(T)案件都有完整的类型,因此失败(即,短线GCC采取不影响结果的情况sizeof或typeid).
所以,总而言之,我认为Clang是三者中最符合标准的(不采取捷径或扩展).