In class static const ODR

2019-02-07 04:56发布

问题:

I am a bit confused by the static in-class initialization of a const member. For example, in the code below:

#include <iostream>

struct Foo
{
    const static int n = 42;
};

// const int Foo::n; // No ODR

void f(const int& param)
{
    std::cout << param << std::endl;
}

int g(const int& param)
{
    return param;
}

template<int N>
void h()
{
    std::cout << N << std::endl;
}

int main()
{
    // f(Foo::n); // linker error, both g++/clang++
    std::cout << g(Foo::n) << std::endl; // OK in g++ only with -O(1,2 or 3) flag, why?!
    h<Foo::n>(); // this should be fine
}

Live example

I do not define Foo::n (the line is commented). So, I expect the call f(Foo::n) to fail at link time, and indeed it does. However, the following line std::cout << g(Foo::n) << std::endl; compiles and links fine only by gcc (clang still emits a linker error) whenever I use an optimization flag such as -O1/2/3.

  1. Why does gcc (tried with gcc5.2.0 and gcc 4.9.3) compile and link the code when the optimization is turned on?
  2. And am I correct to say that the only usage of in-class static const members is in constant expressions, such as template parameters like in the h<Foo::n> call, in which case the code should link?

回答1:

ODR violations do not require a diagnostic, from the draft C++ standard standard section 3.2 [basic.def.odr] (emphasis mine going forward):

Every program shall contain exactly one definition of every non-inline function or variable that is odr-used in that program; no diagnostic required.

So inconsistent behavior at different optimization levels is perfectly conformant behavior.

Informally a variable is odr-used if:

its address is taken, or a reference is bound to it, and a function is odr-used if a function call to it is made or its address is taken. If an object or a function is odr-used, its definition must exist somewhere in the program; a violation of that is a link-time error.

So both f and g will be odr-uses and require a definition.

The relevant C++14 quote on odr-use would be from section [basic.def.odr]:

A variable x whose name appears as a potentially-evaluated expression ex is odr-used by ex unless applying the lvalue-to-rvalue conversion (4.1) to x yields a constant expression (5.19) that does not invoke any nontrivial 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 (4.1) is applied to e, or e is a discarded-value expression [...]

The wording in C++11 is similar, the changes from C++11 to C++14 are reflected in defect report 712.

Before C++11 it is a bit more complicated but in principle the same for this case.



回答2:

I suppose that the compiler performs the following actions during the optimization:

  • The value const static int n is inlined everywhere. No memory is allocated for the variable n, references to it becomes invalid. The function f() need a reference to n so the program is not compiled.

  • The function g is short and simple. It is effectively inlined and optimized. After the optimization, the function g does not need a reference to n, it just returns constant value 42.

The solution is to define the variable outside the class:

struct Foo
{
    const static int n;
};

const int Foo::n = 42;


回答3:

Formally, ODR violations are undefined behaviour, so the compiler may exhibit any behaviour it likes. That's why the behaviour changes with optimization level and compiler- the compiler has no obligation to maintain a particular behaviour.



回答4:

There is no definition at all. GCC 4.9.2 doesn't compile and link that with any flags.

Note, that:

const static int n = 42;

is a declaration and initializer, but not a definition.