Why defining a variable inside a struct doesn't cause link errors?

struct foo{
   int bar1 = 5;
   const static int bar2 = 6;
};

Sharing foo using a header file among multiple translation units doesn't cause a link error for bar1 and bar2, why is that?

To my knowledge, bar1 doesn't even exist until an instance of foo is created and each instance of bar1 will have unique symbol, so no link errors can happen. bar2 is not even a definition, it's a declaration with initializer and needs to be initialized with const int foo::bar2; in only one file so again no link errors can happen.

Referring to this answer: https://stackoverflow.com/a/11301299

Is my understanding correct?


Solution 1:

Violations of The One Definition Rule do not require a diagnostic or a compiler error.

Presuming that bar2 is referenced in some translation unit: some compilers may successfully compile and link the resulting code. Other compilers may not and reject it (presumably at the linking stage). The C++ standard indicates that this is ill-formed, but a formal compiler diagnostic is not mandatory. That's just what the C++ standard says.

The reason why some compilers may let this skate is because they'll inline all the references to the static class member, hence there won't be any unresolved references that produce a failure at the link stage.

Solution 2:

As long as foo::bar2 is not odr-used, there is nothing wrong with the program. You can even use foo::bar2 in ways that are not odr-uses, and it will be fine. For example:

std::cout << foo::bar2 + 1;  // prints 7

This is because foo::bar2 in this expression can simply be replaced with its compile-time constant value of 6. It is not required to have an address, therefore its definition is not required to exist.

As soon as you odr-use foo::bar2 without having provided a definition, the program becomes ill-formed, no diagnostic required. Usually, this will cause a link error. You can see it for example in code like this:

std::vector<int> v;
v.push_back(foo::bar2);

This is an odr-use of foo::bar2 because push_back takes its argument by reference, necessitating the existence of its address. The program will probably not build unless an out-of-line definition of foo::bar2 is provided somewhere.