Assignment to array in C++17
Asked Answered
V

3

9

Here is some code:

int main()
{
    using T = int[3];
    T a;
    a = T{};
}

As far as I can tell, this code is correct according to the C++17 Standard, however every compiler I tried rejected it.

Is this code actually incorrect? If so, by what clauses of the Standard?


My investigation so far: In C and in older versions of C++, the code was incorrect because the assignment operator's left operand must be a modifiable lvalue, which a either wasn't, or it was unclearly specified. But since C++17 a is clearly specified as a modifiable lvalue (C++17 [basic.lval]/7).

The array-to-pointer conversion is not applied here: [expr.ass] doesn't explicitly specify it, and [expr]/9 and [expr]/10 don't seem to apply: the = expects a prvalue as right operand, and a prvalue was provided. (And it expects a glvalue as left operand, and a glvalue was provided). Those clauses apply if a glvalue was supplied where a prvalue was expected or vice versa.

[expr.ass]/3 says the right expression is implicitly converted to the type of the left operand . But since both sides have identical type int[3] no conversion seems to be necessary.

So I see no clauses which would exclude [expr.ass]/2 from applying, that the value of the right-hand side is stored in the object referred to by the left.


The latest draft moves around the clauses that were in [basic.lval]/7 and [expr]/9-10 but doesn't seem to change their meaning, and it even re-words [expr.ass]/2 to be clearer:

In simple assignment (=), the object referred to by the left operand is modified by replacing its value with the result of the right operand.

Vittoria answered 10/8, 2019 at 3:41 Comment(12)
"But since C++17 a is clearly specified as a modifiable lvalue (C++17 [basic.lval]/7)." -- May you quote that relevant text for a handy reference?Revalue
So you want to assign an uninitialized value to your variable ?Blayze
@Revalue see hereVittoria
@SidS T{} initializes all array elements to 0Vittoria
Are you sure this changed in C++17? I don't see what would be the relevant change since C++11 draft.Theomania
@Theomania the "modifiable lvalue" definition in C++14 is different and could perhaps be interpreted to exclude arrays, it says "If an expression can be used to modify the object to which it refers, the expression is called modifiable." But in any case we could answer the question for C++17 and then worry about older versionsVittoria
Well [expr.ass]/3 says "the expression is implicitly converted to the cv-unqualified type of the left operand" and no expression can be implicitly converted to an array type. But I guess that's too subtle.Dumont
The question was stolen from the std-discussion mail list.Manton
@LanguageLawyer 1. I don't read that list, 2. that code is different. a = b; is ill-formed since b is a glvalue, the array-to-pointer conversion is applied ([expr]/9) and then the pointer cannot be implicitly converted to the type of a;Vittoria
@Vittoria [expr.ass] does not require prvalue right operand, so [expr]/9 does not applyManton
@LanguageLawyer The word is expects, not requires, and I would say that it does expect a prvalue right operand. Any glvalue provided would be converted to prvalue. (lvalue-to-rvalue conversion)Vittoria
@Vittoria So, it is you who expect a prvalue, not the operator.Manton
T
8

As far as I can tell, the definition of "modifiable lvalue" is either under-specified in C++, or arrays have been intentionally been specified to be assignable (I suspect that former is true, since no compiler does latter).

The standard (latest draft) says:

[basic.lval]

An lvalue is modifiable unless its type is const-qualified or is a function type.

This is quite concise, but there is no exclusion of arrays.

Furthermore, this hasn't changed through standard versions at least since C++03, which specifies following:

[basic.lval]

11 Functions cannot be modified, but pointers to functions can be modifiable.

12 A pointer to an incomplete type can be modifiable. ...

13 The referent of a const-qualified expression shall not be modified ...

Which is mostly same, except using more descriptive than definitive wording. No exclusion of arrays.


By contrast, C11 standard is crystal clear (quoting N1548 draft):

6.3.2.1 Lvalues, arrays, and function designators

1 ... A modifiable lvalue is an lvalue that does not have array type, ...

Theomania answered 10/8, 2019 at 4:46 Comment(2)
One could argue that assignment to arrays is actually never prohibited, and if that is the case, then ... (mind blown)Casaleggio
As far as I can tell, the definition of "modifiable lvalue" is either under-specified in C++, or arrays have been intentionally been specified to be assignable I think that someone who was introducing the modifiable lvalue definition into the C++ standard confused lvalues with objects.Manton
V
0

Because built-in operators are also governed by [over.built], that is:

The candidate operator functions that represent the built-in operators defined in Clause [expr] are specified in this subclause.

For assignment operator, the forms of the corresponding functions are:
over.built#19

For every triple (L, vq, R), where L is an arithmetic type, and R is a promoted arithmetic type, there exist candidate operator functions of the form

For every pair (T, vq), where T is any type, there exist candidate operator functions of the form

Tvq& operator=(T vq&, T*);

For every pair (T, vq), where T is an enumeration or pointer to member type, there exist candidate operator functions of the form

vq T& operator=(vq T&, T);

Hence, neither of them could be as the candidate function when the corresponding arguments are a, T{}. So, the program should be ill-formed.

Vullo answered 29/10, 2020 at 9:43 Comment(4)
groups.google.com/a/isocpp.org/d/msg/std-discussion/erohPEiZ8ps/…Manton
@LanguageLawyer That may be the absence of the rules for array type in basic.lval#7, because dcl.array#5 indicates that the object of array type should have to be an non-modifiable object.Vullo
I've removed this Note, check newer drafts. The history of modifiable lvalue is funny. AFAIK Richard Smith confused lvalues with objects, told that objects of array types can be modified, that's why lvalues of array types shouldn't be excluded from modifiable lvalues and changed the modifiable lvalue definition. And then he innocently writes in std-discussion that nothing in C++ forbids assignment to arrays. BTW, later he told that he meant that arrays are modifiable in colloquial sense, and in C++, only objects of scalar types can be modified.Manton
@LanguageLawyer I don't think object of array types can be modified. Conversely, its elements can be modified, that means, the sub-objects of an object can be modified does not mean that such a complete object can be modified, they're two concepts.Vullo
E
0

There is no provision in the C++ Standard for the materialization of a prvalue array, as you can see in Note 3 of [class.temporary]/5, which summarizes the cases where these materializations occur.

Endodontist answered 4/1, 2021 at 20:32 Comment(12)
Notes are non-normative; also points 2.1, 2.3 and 2.6 specify some cases of materialization of a prvalue array . The case auto&& b = T{}; would come under point 2.1 , materializing a prvalue array.Vittoria
@Vittoria "Notes are non-normative" See my question here. In reference to your next comment I should have said "no provision for the materialization of arrays in assignments.Endodontist
Which of those clauses covers the materialization in int c; c = int{}; ?Vittoria
[expr,ass]/3Endodontist
That doesn't say anything about temporary materializationVittoria
It doesn't need to. [expr.ass]/3 is present in the Standard since time immemorial. That is, built-in prvalues are being "materialized" for a long time, but the expression "prvalue materialization" was created by Richard Smith very recently, in his paper [P0135R1] (open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0135r1.html).Endodontist
There's no materialization in int c; c = int{};. Materialization creates a new object and yields an xvalue referring to that object, whereas in this assignment no object needs to be created at all. The abstract machine simply computes the value that would be given to the object if one were to be materialized, then writes that value to c.Ely
@Endodontist OK, so you now agree values can be assigned without the standard explicitly needing to say they are materialized. (as Brian also points out) So how is your answer relevant?Vittoria
@Brian You brought a different view from the one I had before your comment, about the concept of materialization. And I have to agree with you on this. But I'm still not convinced about the correctness of M.M's code. Don't you have anything to say about this?Endodontist
@Endodontist I think the standard wording is defective. We all know arrays are not intended to be assignable, but there is no clear wording in the standard to that effect. And M.M's example proves that Richard Smith's proposed fix wouldn't resolve the issue, since, while an array glvalue can't undergo lvalue-to-rvalue conversion, an array prvalue can be produced directly. The standard should be amended to state that the LHS shall not have array type. Separately, someone should write a paper to add missing wording, ideally for all built-in operators, indicating which operands need to be prvalues.Ely
I also think Columbo's proposal has merit, i.e., to simply make arrays assignable (not sure about the wording). The argument presented against such proposal in the thread is not compelling.Ely
@Brian Thanks for the comments above. Very enlightening, especially the first one.Endodontist

© 2022 - 2024 — McMap. All rights reserved.