8

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?
Shafik Yaghmour
  • 154,301
  • 39
  • 440
  • 740
vsoftco
  • 55,410
  • 12
  • 139
  • 252
  • 2
    [odr violations do not require a diagnostic](http://stackoverflow.com/a/28446388/1708801). – Shafik Yaghmour Sep 27 '15 at 21:54
  • Is this specifically a C++11 question? The odr quotes [change a bit between C++11 and C++14](http://stackoverflow.com/a/28846608/1708801) although I don't think it really matters in this case but in some cases it does. – Shafik Yaghmour Sep 27 '15 at 22:24
  • @ShafikYaghmour I compiled using `-std=c++11`, but the question per se is not necessarily C++11 (or C++14) only. O observed the same behaviour with `-std=c++98` also. – vsoftco Sep 27 '15 at 22:25

4 Answers4

6

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;
Andrey Nasonov
  • 2,619
  • 12
  • 26
  • Alternative solutions are to make it `constexpr` or an enum value. A `constexpr` is arguable better, now that compilers support it in general, because it's less source code and because it works nicely in headers. – Cheers and hth. - Alf Sep 27 '15 at 21:21
4

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.

Puppy
  • 144,682
  • 38
  • 256
  • 465
4

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.

Community
  • 1
  • 1
Shafik Yaghmour
  • 154,301
  • 39
  • 440
  • 740
2

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.

Shafik Yaghmour
  • 154,301
  • 39
  • 440
  • 740
Nevermore
  • 1,127
  • 9
  • 12
  • I am aware that that's no definition, however as you can see from the live example, the code is compiled and linked. – vsoftco Sep 27 '15 at 20:57
  • That's strange. I don't have gcc4.9.2, but have 4.9.3 from macports on OS X, and it compiles and links. – vsoftco Sep 27 '15 at 20:58
  • it might be optimizer issue, but there is still no definition. – Nevermore Sep 27 '15 at 21:00
  • Look here: http://melpon.org/wandbox/permlink/o9HLkpgJAyibg36t That's 4.9.2, and it links. And yes, I know that's no definition, that's why I asked *why does gcc compile **and link** the code*? – vsoftco Sep 27 '15 at 21:02
  • 2
    This answer misses the point. There are some situations where the toolchain doesn't care about the missing definition, either because ODR is not invoked or because an optimisation has rendered it moot. The OP has hit one of these cases and would like to know specifically what it is (though personally, I wouldn't bother trying to rationalise about it). – Lightness Races in Orbit Sep 27 '15 at 21:04
  • @LightnessRacesinOrbit I asked mainly because I am not sure what's the use case for such an inline const static initialization. Do you know any other case except template parameter use? And I keep wondering *why* isn't such a declaration treated as a definition? Because of optimization issues? – vsoftco Sep 27 '15 at 21:11
  • @vsoftco: And I quote, _"though personally, I wouldn't bother trying to rationalise about it"_. – Lightness Races in Orbit Sep 27 '15 at 21:11
  • @LightnessRacesinOrbit Boring Sunday for me I guess ;) – vsoftco Sep 27 '15 at 21:12
  • @vsoftco: The use case is to put the declaration & initializer in a header file, and the definition in a .cpp file. This way all translation units that `#include` the header will see and benefit from the initializer (for optimization), while there will still be only one definition, preventing any linking issues. It's very pragmatic. – Arne Vogel Sep 28 '15 at 09:35
  • @ArneVogel Thanks, that's true. What I wanted to say is what's the use case without a definition in the cpp file, if any. – vsoftco Sep 28 '15 at 13:17