是' sizeof(T) '与一个不完整的类型是一个有效的替换失败,按照c++标准?



我在StackOverflow和其他地方看到它出现了几次,decltype(sizeof(T))可以与std::void_t一起使用,以SFINAE关闭T是否完整。这个过程甚至被Raymond Chen在微软的博客《在c++中检测一个类型是否被明确地定义为

》中记录了下来。

我不确定这在技术上是否合法,但我尝试过的所有编译器似乎都没问题。

这个行为是可靠的和定义良好的按照c++标准吗?

我能在标准中找到的唯一指示来自[expr]。Sizeof]/1,其中声明:

sizeof运算符不能应用于函数类型或不完整类型的表达式,不能应用于这些类型的括号名,也不能应用于指定位域的全局值…

然而,我不清楚是否措辞"不得适用">暗示这是"无效的"。

<一口>ℹ️注意:这个问题并不针对任何特定版本的标准,但是比较一下这是否在任何时候发生了变化会很有趣。

不适用

意味着它通常是病态的。在SFINAE上下文中,如果由于导致"无效类型或表达式"而导致某些内容通常是错误的,那么只要它位于"直接上下文中",就会导致替换失败。(c++ 20 [temp.扣除]/8),且未被排除在SFINAE (例如;参见p9关于lambda表达式)。

& 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可以从同一行和列引用,但在两个不同的模板实例化中,以某种方式都决定检查相同的类型,这在一个中是不完整的,在另一个中是完整的。

相关内容

  • 没有找到相关文章

最新更新