C++14增加了变量模板,它定义了相关变量的组。在标准库中变量模板用于访问每个类型特征的值成员:
template<class T>
inline constexpr bool is_arithmetic_v = is_arithmetic<T>::value;
C++17添加了内联变量以更好地支持仅头文件库,这些库可以包含在同一应用程序的多个源文件中(相同的行内变量定义允许在单独的翻译单元中)。但是就变量 * 模板 * 而言,它们被允许在程序中具有多于一个的定义。那么,如果变量模板已经免除了ODR,那么还有什么理由内联地声明它们呢?
只要许多人关注constexpr
与inline constexpr
的差异(这是另一个有趣的问题),我就不想在本次讨论中讨论constexpr
。
template <typename T>
bool myVar = sizeof(T) > 1;
它与以下内容有何不同:
template <typename T>
inline bool myVar = sizeof(T) > 1;
1条答案
按热度按时间toiithl61#
由于链接可以腐烂,科平https://quuxplusone.github.io/blog/2022/07/08/inline-constexpr的内容在这里作为一个答案。我不是一个作者。浏览器插件“Copy selection as markdown“做得很好。
∮ ∮ ∮
C11和C14标准库定义了许多constexpr全局变量,如下所示:
在C17中,所有这些constexpr变量都被重新指定为
inline constexpr
变量。这里的inline
关键字和它在inline
函数中的意思是一样的:“该实体可以在多个TU中定义;所有这些定义是相同的;在链接时将它们合并成一个定义。”如果你在C14中查看这些变量之一的生成代码,你会看到类似于(Godbolt)的内容:然而在C++17中,你会看到这个:
后一个代码片段中的关键词是
comdat
;它的意思是“嘿,linker!不要将所有.rodata._ZSt8in_place
部分的文本连接在一起,而应该删除重复项,这样最终的可执行文件中只包含一个这样的部分!”std::in_place
本身的名称修改还有一个小的区别:作为一个inline constexpr
变量,它会被变形为_ZSt8in_place
,但是作为一个非inline
(因此也是static
)变量,它会被变形为_ZStL8in_place
和L
。GCC在其mangling中区分内部和外部链接符号,以支持DR426之前有效的C++的情况:
在the C++ Slack上,艾德Catmur展示了一个如何观察这种差异的例子,当然这是一个人为的例子,但它确实具体地演示了单纯的
constexpr
(内部链接,每个TU一个实体)和inline constexpr
(外部链接,整个程序一个实体)之间的差异。最后两个命令行的区别在于链接器是先看到
alpha.o
还是main.o
,因此它选择保留alpha.cpp
还是main.cpp
中的inline const void *f()
定义。那么表达式alpha()()
的结果将是x
-from-alpha.cpp
的地址。main
中的Assert将把该地址与x
-from-main.cpp
的地址进行比较。当x
被标记为inline
时,整个程序中只有一个实体x
,所以两个x
是相同的,Assert成功,但是当x
是一个普通的旧constexpr
变量时,有两个不同的(内部链接)x
,有两个不同的地址,所以Assert失败。您可以使用libstdc重现此行为,通过将变量
x
与std::piecewise_construct
这样的C14标准库变量交换。main
中的Assert在使用-std=c++17
编译时将通过,而在使用-std=c++14
编译时将失败。这是因为libstdc++根据语言模式(源代码)有条件地使这些变量成为inline
:另一方面,LLVM/Clang的libc++不会根据语言模式(source)条件化代码:
我推测这样做是为了减少程序部分编译为C14部分编译为C17时可能导致的混乱。程序的行为可能会根据它是编译为C14还是C17而改变(在这个人为的场景中),这已经够糟糕的了;想象一下,如果程序的某些部分认为只有一个
std::piecewise_construct
,而其他部分认为有几个std::piecewise_construct
,那么会有什么样的混乱。类似地,多态类可以使用多重继承来保持相同类型
Animal
的许多基类子对象;或者它可以使用多个虚拟继承来保存Animal
类型的单个虚拟基类子对象。但是,想象一下如果多态类同时从同一类型中虚拟和非虚拟地继承,会有多混乱!(CppCon 2017),我分别使用名称CatDog
和Nemo
表示两个合理的场景,而SiameseCat
-与-Flea
用于易混淆的场景。MISRA-C编码标准explicitly bans用于易混淆的场景(根据MISRA规则10-1-3)。libc积极地放弃了对超过两年的编译器的支持,我希望在某个时候,所有支持的编译器都允许
inline constexpr
作为扩展,即使在C11模式下也是如此,然后libc就可以一次性地将inline
添加到它的所有全局变量中。