What is the purpose of declaring variable templates inline? [duplicate]
Asked Answered
E

1

7

C++14 added variable templates, which define groups of related variables. In the standard library variable templates are used for accessing each type trait's value member:

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

C++17 added inline variables to better support header-only libraries, which can be included in multiple source files within the same application (identical inline variable definitions are allowed in separate translation units). But as far as variable templates are concerned, they are allowed to have more than one definition in a program anyway. So is there any reason to declare variable templates inline if they are already exempt from ODR?


As long as many people have focused on constexpr vs inline constexpr difference, which is another interesting question, I'd like to disregard constexpr for the purpose of this discussion.

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

How does it differ from the following:

template <typename T>
inline bool myVar = sizeof(T) > 1;
Epicycle answered 7/3, 2023 at 8:13 Comment(8)
implicit < explicitHalima
iirc there is a subtle difference, but I don't remeber what it is ;)Pori
iirc it wasn't as clear in the core language when the variables were proposed for the library. Since it's the intent to make them inline, and the specifier is at worst just redudant, it was added anyway.Milliken
oh I remember what i remembered, but its not that relevant here. You cannot declare it once with inline and once without the specifier without breaking odr, even though the definitions would be efffectively the samePori
quuxplusone.github.io/blog/2022/07/08/inline-constexprLaforge
Regardless of any minor technical differences (see other comments for that), I prefer to always add inline to everything that's supposed to be inline - because then I don't have to care about that keyword that's very easy to miss when I refactor things.Catnip
In that particular case with templates involved the inline is just an explicit confirmation of the fact. From the compiler perspective it's superfluous.Licht
The main purpose of declaring global variables 'inline' is that this dispenses you from having to place (implement) them in a .cpp. There is no other reason that I can think of, and it's a very useful feature.Fucus
L
0

Since link can rotten, copping content of https://quuxplusone.github.io/blog/2022/07/08/inline-constexpr here as an answer. I'm not an author. Browser plugin "Copy selection as markdown" did nice job.


An example where inline constexpr makes a difference

The C++11 and C++14 standard libraries defined many constexpr global variables like this:

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*/{});

In C++17, all of these constexpr variables were respecified as inline constexpr variables. The inline keyword here means the same thing as it does on an inline function: “This entity might be defined in multiple TUs; all those definitions are identical; merge them into one definition at link time.” If you look at the generated code for one of these variables in C++14, you’ll see something like this (Godbolt):

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

Whereas in C++17, you’ll see this instead:

  // 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

The critical word in the latter snippet is comdat; it means “Hey linker! Instead of concatenating the text of all .rodata._ZSt8in_place sections together, you should deduplicate them, so that only one such section is included in the final executable!” There’s another minor difference in the name-mangling of std::in_place itself: as an inline constexpr variable it gets mangled into _ZSt8in_place, but as a non-inline (and therefore static) variable it gets mangled into _ZStL8in_place with an L. Clang’s name-mangling code has this to say about the L:

GCC distinguishes between internal and external linkage symbols in its mangling, to support cases like this that were valid C++ prior to DR426:

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

On the C++ Slack, Ed Catmur showed an example of how this difference can be observed. This is a contrived example, for sure, but it does concretely demonstrate the difference between mere constexpr (internal linkage, one entity per TU) and inline constexpr (external linkage, one entity for the entire program).

// 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

The difference between the last two command lines is whether the linker sees alpha.o or main.o first, and therefore whether it chooses to keep the definition of inline const void *f() from alpha.cpp or main.cpp. If it keeps the one from alpha.cpp, then the result of the expression alpha()() will be the address of x-from-alpha.cpp. But, thanks to the inliner, the assertion in main will be comparing that address against the address of x-from-main.cpp. When x is marked inline, there’s only one entity x in the whole program, so the two xs are the same and the assertion succeeds. But when x is a plain old constexpr variable, there are two different (internal-linkage) xs with two different addresses, and so the assertion fails.

You can reproduce this behavior with libstdc++, by swapping the variable x for a C++14 standard library variable like std::piecewise_construct. The assertion in main will pass when compiled with -std=c++17 and fail when compiled with -std=c++14. This is because libstdc++ makes these variables conditionally inline depending on the language mode (source):

#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’s libc++, on the other hand, doesn’t conditionalize their code based on the language mode (source):

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

I speculate that this was done in order to reduce the confusion that could result if a program was compiled partly as C++14 and partly as C++17. It’s bad enough that a program’s behavior can change (in this contrived scenario) depending on whether it’s compiled as C++14 or C++17; imagine the additional confusion if some parts of the program believed there was only one std::piecewise_construct and other parts believed there were several.

Analogously, a polymorphic class can use multiple inheritance to hold many base-class subobjects of the same type Animal; or it can use multiple virtual inheritance to hold a single virtual base-class subobject of type Animal. But imagine the confusion if a polymorphic class inherits both virtually and non-virtually from the same type! In dynamic_cast From Scratch” (CppCon 2017), I used the names CatDog and Nemo, respectively, for the two reasonable scenarios, and SiameseCat-with-Flea for the confusing scenario. The MISRA-C++ coding standard explicitly bans the confusing scenario (by MISRA rule 10-1-3).

libc++ aggressively drops support for compilers older than about two years. I expect that at some point all supported compilers will permit inline constexpr as an extension even in C++11 mode, and then libc++ will be free to add inline to all its global variables in one fell swoop.

Laforge answered 7/3, 2023 at 11:53 Comment(1)
Thank you for the detailed answer, but you seem to be focused on inline vs inline constexpr difference. I'd like to know the difference between variable templates and inline variable templates, regardless of the constexpr keyword.Epicycle

© 2022 - 2024 — McMap. All rights reserved.