This code works:
struct Blob {
static constexpr int a = 10;
};
int main() {
Blob b;
auto c = b.a;
}
But if I change int
to float
I get an error:
struct Blob {
static constexpr float a = 10.0f;
};
/tmp/main-272d80.o: In function
main': main.cpp:(.text+0xe): undefined reference to
Blob::a'
Why can't I use a constexpr float
in that way?
Compiler: Ubuntu clang version 3.5.0-4ubuntu2 (tags/RELEASE_350/final)
Tested on gcc version 4.9.1 (Ubuntu 4.9.1-16ubuntu6) and there were no error.
EDIT:
It will compile if I use -O1, -O2, -O3 or -Os but fails with -O0
C++11 reads
A variable whose name appears as a potentially-evaluated expression is odr-used unless it is an object that satisfies the requirements for appearing in a constant expression (5.19) and the lvalue-to-rvalue conversion (4.1) is immediately applied.
Clearly the l-t-r conversion is immediately applied, and a constexpr
variable of floating point type can appear in constant expressions as per [expr.const]/(2.7.1):
A conditional-expression is a core constant expression unless it involves one of the following as a potentially evaluated subexpression [..]
- an lvalue-to-rvalue conversion (4.1) unless it is applied to
- a glvalue of literal type that refers to a non-volatile object defined with
constexpr
, or that refers to a sub-object of such an object, or
Seems to be a Clang bug.
Interestingly, if we use Blob::a
instead, clang
does not complain:
auto c = Blob::a;
This should not matter for determining if the it is odr-used or not. So this looks like a clang
bug which I can reproduce on clang 3.7 using no optimization only. We can tell this is an odr issue since adding a out of class definition fixes the issue (see it live):
constexpr float Blob::a ;
So when do you need to define a static constexpr class member? This is covered in section 9.4.2
[class.static.data] which says (emphasis mine going forward):
A static data member of literal type can be declared in the class definition with the constexpr specifier; if so, its declaration shall specify a brace-or-equal-initializer in which every initializer-clause that is an assignment-expression is a constant expression. [ Note: In both these cases, the member may appear in constant expressions. —end note ] The member shall still be defined in a namespace scope if it is odr-used (3.2) in the program and the namespace scope definition shall not contain an initializer.
It requires a definition if it is odr-used. Is it odr-used? No, it is not. The original C++11 wording in section 3.2
[basic.def.odr] says:
An expression is potentially evaluated unless it is an unevaluated operand (Clause 5) or a subexpression thereof. A variable whose name appears as a potentially-evaluated expression is odr-used unless it is an object that satisfies the requirements for appearing in a constant expression (5.19) and the lvalue-to-rvalue conversion (4.1) is immediately applied.
a
satisfies both conditions, it is a constant expression and the lvalue-to-rvalue conversion is immediately applied. Defect Report 712 has changed the wording which applies to C++11 since it is a defect report and 3.2
now says:
A variable x whose name appears as a potentially-evaluated expression ex is odr-used unless applying the lvalue-to-rvalue conversion (4.1) to x yields a constant expression (5.19) that does not invoke any non-trivial 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 potential result that matches would be:
If e is an id-expression (5.1.1), the set contains only e.
it is a constant expression and the lvalue-to-rvalue conversion is applied so it is not odr-used.
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