我在StackOverflow和其他地方看到它出现了几次,decltype(sizeof(T))
可以与std::void_t
一起使用,以SFINAE关闭T
是否完整。这个过程甚至被Raymond Chen在微软的博客《在c++中检测一个类型是否被明确地定义为
我不确定这在技术上是否合法,但我尝试过的所有编译器似乎都没问题。
这个行为是可靠的和定义良好的按照c++标准吗?
我能在标准中找到的唯一指示来自[expr]。Sizeof]/1,其中声明:
…
sizeof
运算符不能应用于函数类型或不完整类型的表达式,不能应用于这些类型的括号名,也不能应用于指定位域的全局值…
然而,我不清楚是否措辞"不得适用">暗示这是"无效的"。
<一口>ℹ️注意:这个问题并不针对任何特定版本的标准,但是比较一下这是否在任何时候发生了变化会很有趣。一口>
不适用
& invalid"one_answers";ill-formed"在这种情况下。p8显式地说:"无效类型或表达式是指如果使用替换的参数编写,将是病态的类型或表达式,并且需要进行诊断。"这种措辞从c++ 11开始就出现了。然而,在c++ 03中,无效的表达式不是替换失败。这就是著名的"sfinae"表达式。在编译器实现者充分确信他们能够实现它之后,在c++ 11中添加的特性。
标准中没有规则说sizeof
表达式是SFINAE规则的例外,所以只要在直接上下文中出现无效的sizeof
表达式,SFINAE就会应用。
即时上下文"仍未在标准中明确定义。GCC开发人员Jonathan Wakely的回答解释了其意图。最终,有人可能会在标准中正式定义它。
然而,对于不完整类型的情况,问题是该技术非常危险。首先,如果在相同类型的相同翻译单元中执行了两次完整性检查,则实例化只执行一次;这意味着第二次检查它时,检查的结果仍然是false,因为is_type_complete_v<T>
只是引用前面的实例化。Chen的文章似乎完全错了:GCC、Clang和MSVC的行为方式都是一样的。看到godbolt。在旧版本的MSVC上可能有不同的行为。
第二,如果存在跨翻译单元方差:即is_type_complete_v<T>
在一个翻译单元中实例化,为假,在另一个翻译单元中实例化,为真,则程序为病态NDR。参见c++ 20 [temp.point]/7.
由于这个原因,通常不做完整性检查;相反,库实现者要么说你可以将不完整类型传递给他们的模板,他们会正常工作,要么说你必须传递一个完整类型,但如果你违反了这个要求,行为是未定义的,因为它不能在编译时可靠地检查。
围绕模板实例化规则的一个创造性方法是使用__COUNTER__
宏来确保每次使用类型特征时都有一个新的实例化,和您必须使用内部链接定义is_type_complete_v
模板,以避免跨tu方差的问题。我从这个答案中得到了这个技巧。不幸的是,__COUNTER__
不在标准c++中,但该技术应该在支持它的编译器上工作。
(我研究了c++ 20的source_location
特性是否可以取代该技术中的非标准__COUNTER__
。我认为它不能,因为IS_COMPLETE
可以从同一行和列引用,但在两个不同的模板实例化中,以某种方式都决定检查相同的类型,这在一个中是不完整的,在另一个中是完整的。