Given an example:
#include <iostream>
class A
{
public:
static const int numberOfWheels = 4;
};
// const int A::numberOfWheels;
int main()
{
std::cout << A::numberOfWheels << std::endl;
}
Is it formally undefined behavior(UB) since A::numberOfWheels
is used without its definition? (see also here). As C++03 states:
The member shall still be defined in a namespace scope if it is used in the program and the namespace scope definition shall not contain an initializer.
I found that definition of used in C++03 is rather confusing, as it points to potentially-evaluated expression:
An object or non-overloaded function is used if its name appears in a potentially-evaluated expression.
From my wild guess it excludes expressions such as:
sizeof(A::numberOfWheels) ;
typeid(A::numberOfWheels).name() ;
but not necessarily an expression with overloaded <<
operator like above.
From these two defect reports it seems that was the intent that it should be very similar to odr-used: defect report 48: Definitions of unused static members which says (emphasis mine going forward):
Originally, all static data members still had to be defined outside the class whether they were used or not.
But that restriction was supposed to be lifted so that static data members need not be defined outside the class unless they are used in a manner which requires their definition, in the same manner as namespace-scope variables. In particular, if an integral/enum const static data member is initialized within the class, and its address is never taken, we agreed that no namespace-scope definition was required.
this modified 3.2p2
as follows:
An expression is potentially evaluated unless it appears where an integral constant expression is required (see 5.19 [expr.const] ), is the operand of the sizeof operator (5.3.3 [expr.sizeof] ), or is the operand of the typeid operator and the expression does not designate an lvalue of polymorphic class type (5.2.8 [expr.typeid] ).
and defect report 82: Definition of "using" a constant expression :
The wording in 3.2 basic.def.odr paragraph 2 about "potentially evaluated" is incomplete. It does not distinguish between expressions which are used as "integral constant expressions" and those which are not; nor does it distinguish between uses in which an objects address is taken and those in which it is not. (A suitable definition of "address taken" could be written without actually saying "address".)
But the wording in the standard was not modified to encompassed the stated intent in either defect report and it is not clear why, only added the exception for where a integral constant expression is required.
Update
defect report 454: When is a definition of a static data member required? finally synced up the wording of the standard with the intent expressed in defect report 48 and it says:
As a result of the resolution of core issue 48, the current C++ standard is not in sync with existing practice and with user expectations as far as definitions of static data members having const integral or const enumeration type are concerned. Basically what current implementations do is to require a definition only if the address of the constant is taken. Example:
void f() { std::string s; ... // current implementations don't require a definition if (s.find('a', 3) == std::string::npos) { ... }
To the letter of the standard, though, the above requires a definition of npos, since the expression std::string::npos is potentially evaluated. I think this problem would be easily solved with simple changes to 9.4.2 class.static.data paragraph 4, 9.4.2 class.static.data paragraph 5 and 3.2 basic.def.odr paragraph 3.
So for C++03 section [basic.def.odr] was meant to cover close to what we consider the odr-used rules from C++11. There were subsequent changes to the C++11 rules via defect report 712 which bring C++11 closer to the C++14 rules.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With