Search code examples
c++c++14one-definition-rule

Is static constexpr variable odr-used?


Giving below code, is Foo::FOO1 ODR-used or not?

#include <iostream>
#include <map>
#include <string>

class Foo
{
public:
    static constexpr auto FOO1 = "foo1";
    void bar();
};

void Foo::bar()
{
    const std::map<std::string, int> m = {
        {FOO1, 1},
    };
    for (auto i : m)
    {
        std::cout << i.first << " " << i.second << std::endl;
    }
}

int main()
{
    Foo f;
    f.bar();
    return 0;
}

Compiling the code with -O1 or above, it is OK, but if compile with -O0, I get below error (see coliru example:

undefined reference to `Foo::FOO1'

which indicates that it is ODR-used. Which is it?


I know the above code is built fine with -O, but in a real (and more complex) case:

  • The code compiles and links fine with -O2
  • The code gets the above undefined reference error LinkTimeOptimization (-O2 -flto)

So it indicates that both optimizations (-O) and LinkTimeOptimization (-flto) would affect ODR-use rule? Does this change between C++14 and C++17?


Solution

  • 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.