Static Constexpr Odr-Used or Not

Is static constexpr variable odr-used?

The rule is [basic.def.odr]/4:

A variable x whose name appears as a potentially-evaluated expression ex is odr-used by ex unless applying the lvalue-to-rvalue conversion to x yields a constant expression that does not invoke any non-trivial functions and, if x is an object, ex is an element of the set of potential results of an expression e, where either the lvalue-to-rvalue conversion ([conv.lval]) is applied to e, or e is a discarded-value expression ([expr.prop]).

The first part is obviously satisfied (FOO1 is constexpr so the lvalue-to-rvalue conversion does yield a constant expression without invoking non-trivial functions), but is the second?

We're constructing a map. The relevant constructor there takes an initializer_list<value_type>, which is to say an initializer_list<pair<const string, int>>. pair has a bunch of constructors, but the one that would be invoked here is:

template <class U1, class U2>
constexpr pair(U1&& x, U2&& y); // with U1 = char const*&, U2 = int

The important part here is that we're not directly constructing a string, we're going through this converting constructor of pair, which involves binding a reference to FOO1. That's an odr-use. There's no lvalue-to-rvalue conversion here, nor is this a discarded-value expression.

Basically, when you take the address of something, that's an odr-use - it has to have a definition. So you have to add a definition:

constexpr char const* Foo::FOO1;

Note that, on the other hand, this:

std::string s = FOO1;

would not be an odr-use. Here we're directly invoking a constructor taking a char const* parameter, which would be an lvalue-to-rvalue conversion.


In C++17, we got this new sentence in [dcl.constexpr]:

A function or static data member declared with the constexpr specifier is implicitly an inline function or variable ([dcl.inline]).

This doesn't change anything about odr-use, FOO1 is still odr-used in your program. But it does make FOO1 implicitly an inline variable, so you don't have to explicitly add a definition for it. Pretty cool.


Note also that just because a program compiles and links does not mean that a variable that lacks a definition was not odr-used.

So it indicates that both optimizations (-O) and LinkTimeOptimization (-flto) would affect ODR-use rule?

They do not. Optimizations are cool like that.

template static constexpr definition of odr-used variable

You should use T instead of Packet_A

template<typename T> constexpr int PacketCollection<T>::types[PacketCollection<T>::size];
^ ^

See live example.

Possible ODR-violations when using a constexpr variable in the definition of an inline function (in C++14)

In the OP's example with std::max, an ODR violation does indeed occur, and the program is ill-formed NDR. To avoid this issue, you might consider one of the following fixes:

  • give the doMath function internal linkage, or
  • move the declaration of kTwo inside doMath

A variable that is used by an expression is considered to be odr-used unless there is a certain kind of simple proof that the reference to the variable can be replaced by the compile-time constant value of the variable without changing the result of the expression. If such a simple proof exists, then the standard requires the compiler perform such a replacement; consequently the variable is not odr-used (in particular, it does not require a definition, and the issue described by the OP would be avoided because none of the translation units in which doMath is defined would actually reference a definition of kTwo). If the expression is too complicated, however, then all bets are off. The compiler might still replace the variable with its value, in which case the program may work as you expect; or the program may exhibit bugs or crash. That's the reality with IFNDR programs.

The case where the variable is immediately passed by reference to a function, with the reference binding directly, is one common case where the variable is used in a way that is too complicated and the compiler is not required to determine whether or not it may be replaced by its compile-time constant value. This is because doing so would necessarily require inspecting the definition of the function (such as std::max<int> in this example).

You can "help" the compiler by writing int(kTwo) and using that as the argument to std::max as opposed to kTwo itself; this prevents an odr-use since the lvalue-to-rvalue conversion is now immediately applied prior to calling the function. I don't think this is a great solution (I recommend one of the two solutions that I previously mentioned) but it has its uses (GoogleTest uses this in order to avoid introducing odr-uses in statements like EXPECT_EQ(2, kTwo)).

If you want to know more about how to understand the precise definition of odr-use, involving "potential results of an expression e...", that would be best addressed with a separate question.

Why is the const static variable odr-used here?

make_tuple takes the argument by const int& (since it's a constant lvalue and it takes a T&& argument), and binding a reference to a value is an ODR-use.

Both cases are ill-formed no diagnostic required. At high optimisation levels of gcc for example, the entire program is optimised out and there are no linker errors, where at no optimisation, both statements give linker errors.

To make it not an ODR use, you can convert it to an rvalue:

    // Various ways to force the lvalue-to-rvalue conversion
auto t = std::make_tuple(int(Test::VALUE));
auto t1 = foo(std::make_tuple((void(), Test::VALUE)));
auto t2 = foo(std::make_tuple(+Test::VALUE));

(So std::make_tuple takes an int&& of a temporary)

Or you can make the definition inline (Easiest with constexpr):

class Test {
public:
static constexpr int VALUE = 100;
};

Should static constexpr class member variables be defined even if they are not ODR-used?

This is a bit opinion based IMO, but trying to stay technical... from the definition you don't have to do it, unless it is ODR-used, so it seems trivial. Don't. Also, a user can always provide definition for the context they needs.

As for the rule of thumb, one can also refer to:

3.2 One definition rule. p3

A variable x whose name appears as a potentially-evaluated expression ex is odr-used unless x satisfies
the requirements for appearing in a constant expression (5.19) and, if x is an object, ex is an element of
the set of potential results of an expression e, where either the lvalue-to-rvalue conversion (4.1) is applied
to e, or e is a discarded-value expression (Clause 5). [...]

Taken from c++14 draft.

So I would say: if you predict that your constant will be used in a non-const context I'd provide a definition for convenience. If not, then not. But it's a design decision one has to make.

c++14 static constexpr auto with odr usage

What about passing through a using defined typename ?

template <typename T>
struct Test
{
using someType = decltype(T::foo());

static constexpr someType something{T::foo()};
};

template<typename T>
constexpr typename Test<T>::someType Test<T>::something;

c++11: Why a in-class initialization of a static constexpr not a definition?

Why a in-class initialization of a static constexpr not a definition?

Because of One Definition Rule (ODR). The rule says that there must be exactly one definition of each non-inline non-member and static member variable. Class definitions, due to their nature, are typically included into multiple translation units. If class definition contained a variable definition, then inclusion into multiple translation units would violate the ODR.

Since C++17, the language has inline variables, so you can define such inline variables within class definitions.

constexpr and ODR

The only place I can see any room for question about your case would be whether or not your use of foo qualifies as odr-used or not. Perhaps the easiest way to clarify at least the intent would be to quote the corresponding section of n1337 (immediately followed the official standard, mostly cleaning up some phrasing, such as in this case):

[...] a name can refer to a const object with internal or no linkage if the object has the same literal type in all definitions of D, and the object is initialized with a constant expression (5.19), and the value (but not the address) of the object is used, and the object has the same value in all definitions of D;

Your use clearly meets all these requirements.

  1. foo has type int in every case.
  2. foo is initialized with a constant expression.
  3. you use only the value, not the address, of foo.
  4. foo has the same value in all defintions of widget.

That said, you'd probably be better off changing foos to a std::vector instead:

struct widget { 
std::vector<int> bars;

widget : bars(foo) {}
};


Related Topics



Leave a reply



Submit