内联声明变量模板的目的是什么?

men*_*oom 7 c++ templates variable-templates c++17 inline-variable

C++14 添加了变量模板,它定义了相关变量组。在标准库中,变量模板用于访问每个类型特征的值成员:

template<class T>
inline constexpr bool is_arithmetic_v = is_arithmetic<T>::value;
Run Code Online (Sandbox Code Playgroud)

C++17 添加了内联变量以更好地支持仅头文件库,这些库可以包含在同一应用程序内的多个源文件中(不同的翻译单元中允许相同的内联变量定义)。但就变量模板而言,无论如何它们都可以在程序中具有多个定义。那么,如果变量模板已经免于 ODR,还有什么理由将它们声明为内联呢?


只要很多人都关注constexpr差异inline constexpr,这是另一个有趣的问题,我就想忽略constexpr这次讨论的目的。

template <typename T>
bool myVar = sizeof(T) > 1;
Run Code Online (Sandbox Code Playgroud)

它与以下内容有何不同:

template <typename T>
inline bool myVar = sizeof(T) > 1;
Run Code Online (Sandbox Code Playgroud)

Mar*_*k R 0

由于链接可能会腐烂,因此复制https://quuxplusone.github.io/blog/2022/07/08/inline-constexpr的内容作为答案。我不是作者。\n浏览器插件“将选择复制为降价”做得很好。

\n
\n

inline constexpr一个有所作为的例子

\n

C++11 和 C++14 标准库定义了许多 constexpr 全局变量,如下所示:

\n
constexpr piecewise_construct_t piecewise_construct = piecewise_construct_t();\nconstexpr allocator_arg_t allocator_arg = allocator_arg_t();\nconstexpr error_type error_ctype = /*unspecified*/;\nconstexpr defer_lock_t defer_lock{};\nconstexpr in_place_t in_place{};\nconstexpr nullopt_t nullopt(/*unspecified*/{});\n
Run Code Online (Sandbox Code Playgroud)\n

在 C++17 中,所有这些 constexpr 变量都被重新指定为inline constexpr变量。这里的关键字inline与函数上的含义相同: \xe2\x80\x9cThis 实体可能在多个TUinline中定义;所有这些定义都是相同的;在链接时将它们合并到一个定义中。\xe2\x80\x9d 如果您查看 C++14 中这些变量之一的生成代码,您\xe2\x80\x99 将看到类似这样的内容 ( Godbolt ):

\n
  // constexpr in_place_t in_place{};\n  .section .rodata\n  .type _ZStL8in_place, @object\n  .size _ZStL8in_place, 1\n_ZStL8in_place:\n  .zero 1\n
Run Code Online (Sandbox Code Playgroud)\n

而在 C++17 中,您\xe2\x80\x99 会看到以下内容:

\n
  // inline constexpr in_place_t in_place{};\n  .weak _ZSt8in_place\n  .section .rodata._ZSt8in_place,"aG",@progbits,_ZSt8in_place,comdat\n  .type _ZSt8in_place, @gnu_unique_object\n  .size _ZSt8in_place, 1\n_ZSt8in_place:\n  .zero 1\n
Run Code Online (Sandbox Code Playgroud)\n

后一个片段中的关键词是comdat;这意味着 \xe2\x80\x9cHey 链接器!您不应将所有部分的文本连接.rodata._ZSt8in_place在一起,而应该对它们进行重复数据删除,以便最终的可执行文件中只包含一个这样的部分!std::in_place本身:作为一个inline constexpr变量,它会被损坏_ZSt8in_place,但作为一个非inline(因此static)变量,它会被_ZStL8in_place一个损坏L。Clang\xe2\x80\x99s 名称修改代码对此有这样的说法L

\n
\n

GCC 在其修改中区分内部和外部链接符号,以支持DR426之前有效的 C++ 的情况:

\n
 void test() { extern void foo(); }\n static void foo();\n
Run Code Online (Sandbox Code Playgroud)\n
\n

C++ Slack上,Ed Catmur 展示了如何观察这种差异的示例。当然,这是一个人为的示例,但它确实具体地展示了单纯constexpr(内部链接,每个 TU 一个实体)和inline constexpr(外部链接,整个程序一个实体)之间的区别。

\n
// f.hpp\nINLINE constexpr int x = 3;\ninline const void *f() { return &x; }\nusing FT = const void*();\nFT *alpha();\n\n// alpha.cpp\n#include "f.hpp"\nFT *alpha() { return f; }\n\n// main.cpp\n#include <cassert>\n#include <cstdio>\n#include "f.hpp"\nint main() {\n    assert(alpha() == f);      // OK\n    assert(alpha()() == f());  // Fail!\n    puts("Success!");\n}\n\n$ g++ -std=c++17 -O2 main.cpp alpha.cpp -DINLINE=inline ; ./a.out\nSuccess!\n$ g++ -std=c++17 -O2 alpha.cpp main.cpp -DINLINE=inline ; ./a.out\nSuccess!\n$ g++ -std=c++17 -O2 main.cpp alpha.cpp -DINLINE= ; ./a.out\nSuccess!\n$ g++ -std=c++17 -O2 alpha.cpp main.cpp -DINLINE= ; ./a.out\na.out: main.cpp:5: int main(): Assertion `alpha()() == f()\' failed.\nAborted\n
Run Code Online (Sandbox Code Playgroud)\n

最后两个命令行之间的区别在于链接器是否首先看到alpha.o或,以及因此是否选择保留from或main.o的定义。如果它保留 from 的 1 ,则表达式的结果将是-from-的地址。但是,多亏了内联函数,中的断言将将该地址与-from-的地址进行比较。当标记为 时,整个程序中只有一个实体\xe2\x80\x99,因此两个实体相同,断言成功。但是当是一个普通的旧变量时,有两个不同的 (internal-linkage)具有两个不同的地址,因此断言失败。inline const void *f()alpha.cppmain.cppalpha.cppalpha()()xalpha.cppmainxmain.cppxinlinexxxconstexprx

\n

您可以使用 libstdc++ 重现此行为,方法是将变量替换x为 C++14 标准库变量,例如std::piecewise_construct. 中的断言main在使用 编译时将通过-std=c++17,在使用 编译时将失败-std=c++14inline这是因为 libstdc++根据语言模式(source )有条件地设置这些变量:

\n
#ifndef _GLIBCXX17_INLINE\n# if __cplusplus > 201402L\n#  define _GLIBCXX17_INLINE inline\n# else\n#  define _GLIBCXX17_INLINE\n# endif\n#endif\n\n_GLIBCXX17_INLINE constexpr\n  piecewise_construct_t piecewise_construct =\n    piecewise_construct_t();\n
Run Code Online (Sandbox Code Playgroud)\n

另一方面,LLVM/Clang\xe2\x80\x99s libc++ 不会根据语言模式条件化其代码(源代码):

\n
/* inline */ constexpr\n  piecewise_construct_t piecewise_construct =\n    piecewise_construct_t();\n
Run Code Online (Sandbox Code Playgroud)\n

我推测这样做是为了减少程序部分编译为 C++14、部分编译为 C++17 时可能导致的混乱。它\xe2\x80\x99s 已经足够糟糕了,程序\xe2\x80\x99s 的行为可以改变(在这种人为的场景中),具体取决于它\xe2\x80\x99s 编译为C++14 还是C++17;想象一下,如果程序的某些部分认为只有一个,而其他部分则认为有多个,那么会造成额外的混乱。std::piecewise_construct

\n
\n

类似地,多态类可以使用多重继承来保存许多相同类型的基类子对象Animal;或者它可以使用多个虚拟继承来保存类型的单个虚拟基类子对象Animal。但想象一下,如果多态类从同一类型虚拟继承和非虚拟继承,会造成怎样的混乱!在\xe2\x80\x9c dynamic_castFrom Scratch\xe2\x80\x9d (CppCon 2017) 中,我分别使用名称CatDog和 来Nemo表示两个合理的场景,使用SiameseCat-with-Flea来表示令人困惑的场景。MISRA-C++ 编码标准明确禁止这种令人困惑的情况(根据 MISRA 规则 10-1-3)。

\n
\n

libc++ 积极放弃对大约两年前的编译器的支持。我预计在某个时候,所有受支持的编译器都将允许inline constexpr作为扩展,即使在 C++11 模式下也是如此,然后 libc++ 将可以自由地一次性添加inline到其所有全局变量中。

\n

  • 感谢您的详细回答,但您似乎专注于内联与内联 constexpr 的差异。我想知道变量模板和内联变量模板之间的区别,无论 constexpr 关键字如何。 (2认同)