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

slhcrj9b  于 2023-03-09  发布在  其他
关注(0)|答案(1)|浏览(136)

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

template<class T>
inline constexpr bool is_arithmetic_v = is_arithmetic<T>::value;

C++17添加了内联变量以更好地支持仅头文件库,这些库可以包含在同一应用程序的多个源文件中(相同的行内变量定义允许在单独的翻译单元中)。但是就变量 * 模板 * 而言,它们被允许在程序中具有多于一个的定义。那么,如果变量模板已经免除了ODR,那么还有什么理由内联地声明它们呢?
只要许多人关注constexprinline constexpr的差异(这是另一个有趣的问题),我就不想在本次讨论中讨论constexpr

template <typename T>
bool myVar = sizeof(T) > 1;

它与以下内容有何不同:

template <typename T>
inline bool myVar = sizeof(T) > 1;
toiithl6

toiithl61#

由于链接可以腐烂,科平https://quuxplusone.github.io/blog/2022/07/08/inline-constexpr的内容在这里作为一个答案。我不是一个作者。浏览器插件“Copy selection as markdown“做得很好。
∮ ∮ ∮
C11和C14标准库定义了许多constexpr全局变量,如下所示:

constexpr piecewise_construct_t piecewise_construct = piecewise_construct_t();
constexpr allocator_arg_t allocator_arg = allocator_arg_t();
constexpr error_type error_ctype = /*unspecified*/;
constexpr defer_lock_t defer_lock{};
constexpr in_place_t in_place{};
constexpr nullopt_t nullopt(/*unspecified*/{});

在C17中,所有这些constexpr变量都被重新指定为inline constexpr变量。这里的inline关键字和它在inline函数中的意思是一样的:“该实体可以在多个TU中定义;所有这些定义是相同的;在链接时将它们合并成一个定义。”如果你在C14中查看这些变量之一的生成代码,你会看到类似于(Godbolt)的内容:

// constexpr in_place_t in_place{};
  .section .rodata
  .type _ZStL8in_place, @object
  .size _ZStL8in_place, 1
_ZStL8in_place:
  .zero 1

然而在C++17中,你会看到这个:

// inline constexpr in_place_t in_place{};
  .weak _ZSt8in_place
  .section .rodata._ZSt8in_place,"aG",@progbits,_ZSt8in_place,comdat
  .type _ZSt8in_place, @gnu_unique_object
  .size _ZSt8in_place, 1
_ZSt8in_place:
  .zero 1

后一个代码片段中的关键词是comdat;它的意思是“嘿,linker!不要将所有.rodata._ZSt8in_place部分的文本连接在一起,而应该删除重复项,这样最终的可执行文件中只包含一个这样的部分!”std::in_place本身的名称修改还有一个小的区别:作为一个inline constexpr变量,它会被变形为_ZSt8in_place,但是作为一个非inline(因此也是static)变量,它会被变形为_ZStL8in_placeL
GCC在其mangling中区分内部和外部链接符号,以支持DR426之前有效的C++的情况:

void test() { extern void foo(); }
 static void foo();

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

// f.hpp
INLINE constexpr int x = 3;
inline const void *f() { return &x; }
using FT = const void*();
FT *alpha();

// alpha.cpp
#include "f.hpp"
FT *alpha() { return f; }

// main.cpp
#include <cassert>
#include <cstdio>
#include "f.hpp"
int main() {
    assert(alpha() == f);      // OK
    assert(alpha()() == f());  // Fail!
    puts("Success!");
}

$ g++ -std=c++17 -O2 main.cpp alpha.cpp -DINLINE=inline ; ./a.out
Success!
$ g++ -std=c++17 -O2 alpha.cpp main.cpp -DINLINE=inline ; ./a.out
Success!
$ g++ -std=c++17 -O2 main.cpp alpha.cpp -DINLINE= ; ./a.out
Success!
$ g++ -std=c++17 -O2 alpha.cpp main.cpp -DINLINE= ; ./a.out
a.out: main.cpp:5: int main(): Assertion `alpha()() == f()' failed.
Aborted

最后两个命令行的区别在于链接器是先看到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重现此行为,通过将变量xstd::piecewise_construct这样的C14标准库变量交换。main中的Assert在使用-std=c++17编译时将通过,而在使用-std=c++14编译时将失败。这是因为libstdc++根据语言模式(源代码)有条件地使这些变量成为inline

#ifndef _GLIBCXX17_INLINE
# if __cplusplus > 201402L
#  define _GLIBCXX17_INLINE inline
# else
#  define _GLIBCXX17_INLINE
# endif
#endif

_GLIBCXX17_INLINE constexpr
  piecewise_construct_t piecewise_construct =
    piecewise_construct_t();

另一方面,LLVM/Clang的libc++不会根据语言模式(source)条件化代码:

/* inline */ constexpr
  piecewise_construct_t piecewise_construct =
    piecewise_construct_t();

我推测这样做是为了减少程序部分编译为C14部分编译为C17时可能导致的混乱。程序的行为可能会根据它是编译为C14还是C17而改变(在这个人为的场景中),这已经够糟糕的了;想象一下,如果程序的某些部分认为只有一个std::piecewise_construct,而其他部分认为有几个std::piecewise_construct,那么会有什么样的混乱。
类似地,多态类可以使用多重继承来保持相同类型Animal的许多基类子对象;或者它可以使用多个虚拟继承来保存Animal类型的单个虚拟基类子对象。但是,想象一下如果多态类同时从同一类型中虚拟和非虚拟地继承,会有多混乱!(CppCon 2017),我分别使用名称CatDogNemo表示两个合理的场景,而SiameseCat-与-Flea用于易混淆的场景。MISRA-C编码标准explicitly bans用于易混淆的场景(根据MISRA规则10-1-3)。
libc
积极地放弃了对超过两年的编译器的支持,我希望在某个时候,所有支持的编译器都允许inline constexpr作为扩展,即使在C11模式下也是如此,然后libc就可以一次性地将inline添加到它的所有全局变量中。

相关问题