So, prior to C++11, in-class member initialization of non-const and non-static variables was not allowed.
Bjarne Stroustrup's reasoning was that the one-definition rule "would be broken if C++ allowed in-class definition of entities that needed to be stored in memory as objects."
What did he mean by this?
I do understand that the one-definition rule mandates that only one definition of an entity that exists in memory should be allowed per program. But I still cannot see why the rule would be broken.
My question is related to this question, but I'm asking a different question, specifically for clarification on how the one-definition rule would have gotten broken.
I watched a Back to Basics talk by Bob Steagall - "The Structure of a Program". And looked at some other related questions. But I still cannot understand Stroustrup's assertion.
As I read it, Stroustrup is only talking about in-class initializers for static data members. (And the rules there were not actually relaxed much until C++17.) I'm not aware of him having given any reason why C++03 didn't have default member initializers for non-static data members.
Typically, class definitions will be placed in headers because any translation unit that wants to call a method on a class must be able to see the class definition (not merely a forward declaration). But for any one given class, say C
, and any one given static data member, say x
, there should be only one copy of C::x
in the entire program. In order to make this the case, it was necessary to say that the in-class declaration of x
is not a definition of x
(doesn't actually set aside static storage for x
). If it were a definition, you would end up with multiple definitions (an ODR violation) due to multiple translation units including the header.
Even when you have something like struct C { static const int x = 3; }
it's still not a definition of C::x
. If you want to provide a definition of x
, then you must select a single translation unit in which to place const int C::x;
. However, as a special case, this particular kind of in-class initializer (i.e. a constant expression used to initialize a const
static data member of integral or enumeration type) is useful even if there is no definition, because the compiler can see that its value is always 3, so wherever you use C::x
in the program, the compiler can simply replace it with 3. (But if you take the address of C::x
, or pass it to a parameter of type const int&
, or something like that which requires an address, then you must have a definition somewhere in the program.) If the static data member is not const
or its initializer is not a constant expression, then having an in-class initializer would not make any sense because the in-class declaration (which, again, is not a definition) doesn't actually allocate any storage to initialize. So this construct was not allowed.
C++11 introduced the constexpr
keyword, which made it possible to have function calls (to constexpr
functions only) in in-class static data member initializers, and to have in-class static data member initializers for const
members of a broader range of types. For backward compatibility, the special rule that you could have an in-class initializer for a static data member of const
integral and enumeration types (without having to declare them constexpr
) was retained, but not extended to other types.
In order to be able to have arbitrary in-class static data member initializers, you need to have some way of saying that each such in-class declaration is a definition, but each of these definitions actually defines the same object with no ODR violation. This feature, called inline variables, was added in C++17 (although compilers supported a form of it even in C++03: a local static declared in an inline function, or an instance of a static data member of a class template, has inline semantics). In C++17 you can explicitly declare a static data member to be inline
, which allows you to provide an in-class initializer. constexpr
static data members were also made implicitly inline.