dlf
dlf

Reputation: 9383

Why isn't the ODR violated by taking the address of an inline-defined static const integral member variable?

Something like this would be an obvious violation of the C++ one definition rule if it compiled:

// Case 1
// something.h

struct S {};

struct A
{
   static const S val = S();
};

because if something.h were include in more than one module, the definition of A::val would be repeated. However, this is allowed:

// Case 2    
// someOtherThing.h

struct B
{
   static const int val = 3;
};

As I understand it, the reason this case is ok is because B::val is a compile-time constant of integral type, so the compiler can essentially do a search-and-replace of all references to B::val with a literal 3 (and examination of the disassembly shows this is exactly what it does). Therefore, there are in a sense zero definitions of B::val in the final product, so the ODR does not apply. However, consider this:

// Case 3
// yetAnotherThing.h

struct C
{
   static const int val = 3;

   const int* F()
   {
      return &val;
   }
};

This is permitted, and the disassembly shows that in this case, some memory location has actually been set aside to store the value of C::val. On the surface, this means we now violate the ODR if yetAnotherThing.h is included in multiple modules since static const int val = 3 now causes storage to be "emitted". Yet neither the compiler nor the linker (VC++2012) complains.

Why? Is this just an obnoxious special case that authors of compilers/linkers have to deal with? And if so, why can't the same system be used to make case #1 work too?

(Relevant quotes from the standard are welcome, but they won't necessarily answer the question. If the standard said that any usage of the pink_elephants keyword should cause every instance of the number 42 to be replaced with 666 then that would be that, but we'd still be left wondering why such a weird rule exists.)

Upvotes: 1

Views: 256

Answers (1)

James Kanze
James Kanze

Reputation: 153929

Your first example is not a violation of the ODR, because the declaration of a static member inside a class is not a definition, only a declaratin. The static member must be defined in a single translation unit, e.g.:

S const A::val;

in a source file (not a header).

In pre-C++11, when the declaration was static, had integral type and was const, it was permitted (as a special exception) to specify an initializer, provided the initializer was a constant integral expression. Even in this case, however, you formally needed the definition (without an initializer) in one, and only one source file. If it was missing, the results were undefined behavior. (IIFC, with one exception: if the variable was only used in contexts which required an integral constant expression, no definition was required.)

I think C++11 has extended this some, and allows some non-integral types as well to have an initializer in the class definition. But it still requires the definition outside of the class.

Concerning your final example, which you claim works: it is not legal in pre-C++11, and it causes errors in many compilers. (I was under the impression that C++11 made it legal, and left it up to the compiler to generate the instance, but I can't find the appropriate words off-hand. If it was made legal in C++11, then this is just a C++11 feature which VC++2012 implements.)

Upvotes: 3

Related Questions