Unfortunately, I am somewhat confused about constexpr
, global constants declared in header files, and the odr.
In short: Can we conclude from here
https://isocpp.org/files/papers/n4147.pdf
that
constexpr MyClass const MyClassObj () { return MyClass {}; } constexpr char const * Hello () { return "Hello"; }
is preferable over
constexpr MyClass const kMyClassObj = MyClass {}; constexpr char const * kHello = "Hello";
for defining globals in a header file if I want to "just use" those globally declared/defined entities and do not want to think about how I use them?
In other words, you should use constexpr for your constants in header files, if possible, otherwise const . And if you require the address of that constant to be the same everywhere mark it as inline .
constexpr creates a compile-time constant; const simply means that value cannot be changed.
The keyword constexpr was introduced in C++11 and improved in C++14. It means constant expression. Like const , it can be applied to variables: A compiler error is raised when any code attempts to modify the value. Unlike const , constexpr can also be applied to functions and class constructors.
Note: as of C++17, you can declare your variables as inline.
TL;DR: If you want to be on the (very) safe side, go with constexpr functions. It isn't inherently necessary though, and certainly won't be if you're performing trivial operations on these objects and are solely interested in their value, or simply don't use them in the dangerous scenarios listed below.
The fundamental issue is that const
variables at namespace scope such as yours (generally) have internal linkage ([basic.link]/(3.2)). This implies that each translation unit compiling the corresponding header will observe a different entity (i.e. symbol).
Now imagine we have a template or inline function in a header using those objects. The ODR is very precise about this scenario - [basic.def.odr]/6:
"initialized with a constant expression" is certainly met, since we're talking constexpr
. So is "the object has the same value in all definitions of D
" if you don't monkey about.
"the object isn't odr-used" is probably the only questionable condition. Basically, it requires that you don't necessitate the variables runtime existence as a symbol, which in turn implies that
You don't bind it to a reference (=> you don't forward it!)
You don't (neither explicitly nor implicitly) take its address.
The only exception to the second rule are arrays, which can be taken the address of implicitly inside a subscript operation as long as the two above rules aren't violated for the yielded glvalue.
More precisely, odr-use is governed by [basic.def.odr]/3:
A variable
x
whose name appears as a potentially-evaluated expressionex
is odr-used byex
unless applying the lvalue-to-rvalue conversion (4.1) tox
yields a constant expression (5.20) that does not invoke any non-trivial functions and, ifx
is an object,ex
is an element of the set of potential results of an expressione
, where either the lvalue-to-rvalue conversion (4.1) is applied toe
, ore
is a discarded-value expression (Clause 5).
Applying l-t-r to any constexpr
variable will behave as required by the first part. The second part requires that the variable be used as a value rather than an actual object; that is, it's eventually either discarded or directly evaluated, giving the above rules of thumb.
If you avoid odr-use of the variable inside inline functions, templates or the like, you're fine. But if you use the return value of a corresponding constexpr function, you won't have to worry, since prvalues are already behaving more like values/literals (not objects) and constexpr functions are inline and definitely won't violate the ODR (if you don't use constexpr
variables inside there!).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With