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)