11

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?

12

1 Answer 1

10

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.

Sign up to request clarification or add additional context in comments.

6 Comments

Some more clarification about declaration vs definition. What OP has in his TryMe struct may appear to be a definition, but in fact is a declaration because of the funny rules about when a declaration is also a definition. Here OP has a static variable with an in-class member initializer, which is usually disallowed, but is allowed here because of constexpr (and int). However, OP hasn't actually allocated memory for TryMe::arr because the variable is also static which requires external storage. So, like any static variable, OP still must provide a declaration outside the struct.
(Continuing previous comment because I ran out of room). However, it's not a good idea to provide the definition constexpr int TryMe::arr[1]; in the same file that OP defines the TryMe struct because any class that wants to include the definition for struct TryMe will then attempt to allocate storage for TryMe::arr[1], violating the One Definition Rule. So what usually happens instead is that you would put the constexpr int TryMe::arr[1] in a .cpp file of some sort, separate from the header.
@AndyG fair point, I assumed some knowledge, I should have added more detailed. Let me add more details.
I thought your post was great! I just wanted to add some extra detail for those lurkers who happened upon it, so it wasn't a jab at you.
@AndyG no worries, I always welcome constructive comments. You pointed out important details that I did not think to point out.
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.