This is a follow up question to Undefined reference to static constexpr char[][].
The following program builds and runs fine.
#include <iostream>
struct A {
constexpr static char dict[] = "test";
void print() {
std::cout << A::dict[0] << std::endl;
}
};
int main() {
A a;
a.print();
return 0;
}
However, if I change A::print() to:
void print() {
std::cout << A::dict << std::endl;
}
I get the following linker error in g++ 4.8.2.
/tmp/cczmF84A.o: In function `A::print()':
socc.cc:(.text._ZN1A5printEv[_ZN1A5printEv]+0xd): undefined reference to `A::dict'
collect2: error: ld returned 1 exit status
The linker error can be resolved by adding a line:
constexpr char A::dict[];
outside the class definition.
However, it's not clear to me why using one of the members of the array does not cause a linker error while using the array causes a linker error.
The standard does not require any diagnostics for a failure to provide a definition where one is required.
3.2 One definition rule [basic.def.odr]
4 Every program shall contain exactly one definition of every non-inline function or variable that is odr-used in that program; no diagnostic required. [...]
This means implementations are allowed to optimise away accesses to such variables, and that's what's happening in your first case with GCC.
Both GCC and clang have decided that they prefer a consistent user experience, where error messages about missing definitions do not depend on the optimisation level. Usually, that means that any missing definition causes an error message. However, in this case, GCC is doing some minimal optimisation even at -O0, avoiding the error.
But the program is an error either way, because even A::dict[0] is an ODR-use:
3.2 One definition rule [basic.def.odr]
3 A variable x whose name appears as a potentially-evaluated expression ex is odr-used by ex 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 (Clause 5). [...]
The use of A::dict doesn't involve lvalue-to-rvalue conversion, it involves the array-to-pointer conversion, so the exception doesn't apply.
In addition to the information provided by #hvd in his answer...
From the C++ Draft Standard N3337 (emphasis mine):
9.4.2 Static data members
3 If a non-volatile const static data member is of integral or enumeration type, its declaration in the class definition can specify a brace-or-equal-initializer in which every initializer-clause that is an assignment- expression is a constant expression (5.19). 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.
Given that A::data is odr-used in the expression A::data[0], as per the standard, it shall be defined in a namespace scope. The fact that g++ is able to successfully create a program without A::data being defined in a namescpace scope does not make the program right. To be standards compliant, A::data shall be defined.
Related
The question is about the C++ documentation and standard documentation. Is in the following code the variable x odr-used?
extern int x;
template<class T> T f() { return x; }
It seems to me it is not used, bud where it is stated in documents? (there is the statement, that appearance x in expression is odr-use, but...)
It is odr-used. [basic.def.odr]/2:
An expression is potentially evaluated unless it is an unevaluated operand or a subexpression thereof.
Thus the expression x is potentially evaluated.
[basic.def.odr]/4:
A variable x whose name appears as a potentially-evaluated expression ex is odr-used by ex unless applying the lvalue-to-rvalue conversion ([conv.lval]) to x yields a constant expression ([expr.const]) [...]
We can stop here. Applying the lvalue-to-rvalue conversion to x does not yield a constant expression.
This violation of the ODR does not require a diagnostic ([basic.def.odr]/10):
Every program shall contain exactly one definition of every non-inline
function or variable that is odr-used in that program outside of a
discarded statement; no diagnostic required.
Even assuming for the sake of argument that f does not odr-use x until/unless a specialization is generated, it still won't help the program, because the program is also ill-formed NDR if "no valid specialization can be generated for a template" ([temp.res]/8), and since every valid specialization of f undoubtedly odr-uses x, no valid specialization can be generated if a definition of x is not present in the program.
Given the code snippet:
struct S {
static const int var = 0;
};
int function(const int& rVar){
return rVar;
}
int main()
{
return function(S::var);
}
Compiled with gcc 5.4.0:
g++ -std=c++17 main.cpp -o test
results in the following linkage error:
/tmp/ccSeEuha.o: In function `main':
main.cpp:(.text+0x15): undefined reference to `S::var'
collect2: error: ld returned 1 exit status
§3.3 from the ISO Standard C++17 draft n4296 states:
A variable x whose name appears as a potentially-evaluated expression ex is odr-used by ex unless applying
the lvalue-to-rvalue conversion (4.1) to x yields a constant expression (5.20) 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 [bold-type formatting added], or e is a discarded-value expression (Clause 5).
Q: Why is a definition of the variable var required here? Isn't var denoting an integer object that appears in the potentially-evaluted expression S::var of an outter function call expression, which indeed takes a parameter by reference, but to which finally a lvalue-to-rvalue conversion is applied, and, thus isn't odr-used as stated in the paragraph?
but to which finally a lvalue-to-rvalue conversion is applied, and, thus isn't odr-used as stated in the paragraph?
The lvalue-to-rvalue conversion in the other expression is irrelevant I believe. There is no lvalue-to-rvalue conversion applied to subexpression S::var in the expression function(S::var), thus the exception does not apply.
Considering from the common sense point of view, rather than analysing the rule: functioncould be defined in another translation unit, so the compiler cannot necessarily know how the reference would be used. It cannot just send a copy of the value to the function and hope that the function definition won't use the object in a way that would require the definition of the referred object. Likewise, when compiling the function, the compiler cannot assume that all function calls would send anything other than a reference to an object that exists.
Technically, I suppose that there could be yet more complicated exception for reference arguments of inline functions, but there isn't such exception in the standard. And there shouldn't be since it would make inline expansion mandatory in those cases. In practice, a compiler might behave exactly as such exception would require if it happens to expand the function inline, since odr violations have undefined behaviour.
It is explicit in the example show just above the paragraph you quoted: in function(S::var);, S::var is odr-used.
The reason is that as function takes its parameter by ref (and not by value), no lvalue to rvalue conversion occurs.
But if you change function to take its argument by value:
int function(const int rVar){
return rVar;
}
then the lvalue to rvalue conversion occurs, and S::var is non longer odr-used. And the program no longer exhibit the undefined reference...
The following code compiles ok with Intel-2015 but fails with gcc 4.8.3
Who is right?
#include <iostream>
void f( int const& x ) { std::cout << x << std::endl; }
struct S
{
static constexpr int ce = 42;
};
int main()
{
f(S::ce);
return 0;
}
g++ error:
/tmp/ccOIxa2V.o: In function `main':
test_constexpr.cpp:(.text+0x36): undefined reference to `S::ce'
collect2: error: ld returned 1 exit status
Because the function f takes a reference argument, there has to be a definition of S::ce that a reference can point to at runtime; the compiler can't just replace the argument with a literal 42. So you have to add an out-of-class definition:
const int S::ce;
just like you would with a non-constexpr variable. This allocates a memory location for the value at runtime, for use by references and other things that couldn't be computed at compile time.
See this GCC bug report (which has comments explaining why it isn't actually a bug) for more information.
I think GCC is right. BTW, CLang 3.5.1 gives the same error.
The thing is that constant static variables are allowed not to be defined only if their address is not taken and they are not bound to references.
Your example bounds a reference to it, so the explicit definition is needed.
From the C++11 draft (9.4.2.3), conveniently edited:
A static data member of literal type can be declared in the class definition with the constexpr specifier; [...] The member shall still be defined in a namespace scope if it is odr-used (3.2) in the program [...].
And in 3.2:
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.
That lvalue-to-rvalue conversion stuff is, roughly speaking, done everytime the variable is used except when binding a reference or as argument to the unary take-address operator &.
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 toBlob::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.
How come that the following works on gcc but doesn't on clang, (see it live):
constexpr int giveMeValue() { return 42; }
struct TryMe {
static constexpr int arr[1] = {
giveMeValue()
};
};
int main() {
int val = TryMe::arr[0];
return val;
}
I get an unresolved external symbol with clang.
Is TryMe::arr[0] an object? If it is, is it odr-used?
TryMe::arr is odr-used but you don't provide a definition (see it live):
constexpr int TryMe::arr[1];
Why is the result inconsistent between gcc and clang? This is because odr violations do not require a disagnostic, from both the C++11 and C++14 draft standard (emphasis mine):
Every program shall contain exactly one definition of every non-inline
function or variable that is odr-used in that program; no diagnostic
required.
We can see it is odr-used from the draft C++11 standard, section 3.2 which 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.
TryMe::arr is an object and it does satisfy the requirements for appearing in a constant expression but the lvalue-to-rvalue conversion is not immediately applied to TryMe::arr but to TryMe::arr[0].
The updated wording from the draft C++14 standard which applies to C++11 as well since it was applied via a defect report(DR 712):
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 results of the expression TryMe::arr[0] is empty by the criteria in 3.2 paragraph 2 and so it is odr-used.
Note: you need to provide a definition outside of the class as per section 9.4.2 [class.static.data] which says (emphasis mine):
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
Update
T.C. pointed out defect report 1926 which adds the following bullet to 3.2 [basic.def.odr] paragraph 2:
If e is a subscripting operation (5.2.1 [expr.sub]) with an array operand, the set contains that operand.
Which means subscripting an array is no longer an odr-use and so the OPs code would be well-formed in C++1z and it seems like C++14 since the defect looks like it is against C++14.