cplusplus / CWG

Core Working Group
23 stars 7 forks source link

CWG2879 [expr.const.cast] Clean up specification of `const_cast` #526

Open t3nsor opened 5 months ago

t3nsor commented 5 months ago

Full name of submitter: Brian Bi

Reference (section label): [expr.const.cast]

Issue description: There are a few issues here.

The current specification allows constructs like const_cast<int>(0) and const_casts involving function pointers and pointers to member functions, but this is probably unintentional, and no implementations accept them. We need to say that a cast to object type is allowed only if the object type is an object pointer or pointer to data member type.

"The result of a const_cast refers to the original entity" in paragraph 3 is wrong; it should say "points". Also, the sentence about null pointers is 2 paragraphs down, which is odd.

CWG891, adopted for C++11, made const_cast<int&&>(2) illegal. However, [expr]/7 now says:

Whenever a prvalue appears as an operand of an operator that expects a glvalue for that operand, the temporary materialization conversion is applied to convert the expression to an xvalue.

and [expr.const.cast]/4.2 says:

a glvalue of type T1 can be explicitly converted to an xvalue of type T2 using the cast const_cast<T2&&> [...]

Taken together, this implies that const_cast<int&&> expects a glvalue operand, so a prvalue can be materialized to produce the expected glvalue, which contradicts the intent.

Also, the resolution of CWG891 for some reason treats class prvalues and array prvalues unequally. We may want to fix this. The suggested resolution below leaves the current behavior alone, but it would be easy to amend it if we want a change in this area.

Suggested resolution:

Strike the last two sentences from [expr.const]/1:

The result of the expression const_cast(v) is of type T. If T is an lvalue reference to object type, the result is an lvalue; if T is an rvalue reference to object type, the result is an xvalue; otherwise, the result is a prvalue and the lvalue-to-rvalue, array-to-pointer, and function-to-pointer standard conversions are performed on the expression v. ~Conversions that can be performed explicitly using const_cast are listed below. No other conversion shall be performed explicitly using const_cast.~

Replace the text in [expr.const]/3 with the following, leaving the examples:

If T is an object pointer type or pointer to data member type, the type of v shall be similar to T and the corresponding P_i components of the qualification decompositions of T and the type of v shall be the same ([conv.qual]). If v is a null pointer or null member pointer, the result is a null pointer or null member pointer, respectively. Otherwise, the result points to or past the end of the same object or member, respectively, as v.

Replace [expr.const]/4 with:

Otherwise, T shall be a reference type. Let T1 be the type of v and T2 be the type referred to by T. A const_cast from "pointer to T1" to "pointer to T2" shall be valid. If T is an lvalue reference type, v shall be an lvalue. Otherwise, if T2 is a class type and v is a prvalue, the temporary materialization conversion ([conv.rval]) is applied. Otherwise, the temporary materialization conversion is not applied and v shall be a glvalue. The result refers to the same object as the (possibly converted) operand.

Strike [expr.const]/5:

~A null pointer value ([basic.compound]) is converted to the null pointer value of the destination type. The null member pointer value ([conv.mem]) is converted to the null member pointer value of the destination type.~

frederick-vs-ja commented 5 months ago

and no implementations accept them

Hmm... MSVC currently accepts them (Godbolt link).

See also #232.

t3nsor commented 5 months ago

Ah good catch, thanks. I must have forgotten to test MSVC accidentally.

I think these cases should be disallowed since they seem to have been allowed unintentionally by CWG330.

jensmaurer commented 5 months ago

CWG2879

I don't like the proposed drafting. The reason why we say "can be converted" is that we need to express "we lock into this case, and it may turn out to be ill-formed" for purposes of reinterpret_cast, because there we take the first option that matches (even if it turns out to be ill-formed, eventually). The proposed drafting loses that property.

t3nsor commented 5 months ago

Good point. Then, perhaps it would be easier to just say we don't do the temporary materialization conversion in the other case.

Edit p1:

The result of the expression const_cast(v) is of type T. If T is an lvalue reference to object type, the result is an lvalue; if T is an rvalue reference to object type, the result is an xvalue; otherwise, the result is a prvalue and the lvalue-to-rvalue, array-to-pointer, and function-to-pointer standard conversions are performed on the expression v. The temporary materialization conversion ([conv.rval]) is not performed on v, other than as specified below. Conversions that can be performed explicitly using const_cast are listed below. No other conversion shall be performed explicitly using const_cast.

Edit p3:

For two similar object pointer or pointer to data member types T1 and T2 ([conv.qual]), a prvalue of type T1 ~may~ can be explicitly converted to the type T2 using a const_cast if, considering the qualification-decompositions of both types, each P_i^1 is the same as P_i^2 for all i. ~The result of a const_cast refers to the original entity.~ If v is a null pointer or null member pointer, the result is a null pointer or null member pointer, respectively. Otherwise, the result points to or past the end of the same object or member, respectively, as v.

Edit p4:

For two object types T1 and T2, if a pointer to T1 can be explicitly converted to the type "pointer to T2" using a const_cast, then the following conversions can also be made:

  • an lvalue of type T1 can be explicitly converted to an lvalue of type T2 using the cast const_cast<T2&>;
  • a glvalue of type T1 can be explicitly converted to an xvalue of type T2 using the cast const_cast<T2&&>; and
  • if T1 is a class type, a prvalue of type T1 can be explicitly converted to an xvalue of type T2 using the cast const_cast<T2&&>. The temporary materialization conversion is performed on v.

The result ~of a reference const_cast refers to the original object if the operand is a glvalue and to the result of applying the temporary materialization conversion ([conv.rval]) otherwise.~ refers to the same object as the (possibly converted) operand.

Strike p5 as before.

jensmaurer commented 5 months ago

Updated: CWG2879