[expr.static.cast]/4 seems to be incorrect in C++1z

148 views
Skip to first unread message

Belloc

unread,
Sep 18, 2016, 10:39:46 AM9/18/16
to ISO C++ Standard - Discussion
Consider the following snippet. It compiles in C++14 and AFAIK it should continue to compile after the changes introduced by P0135R1.

class A{};
A f
() { return A(); }
A
&& a = static_cast<A&&>(f());


Note that [expr.static.cast]/3 used to allow the conversion above, but not anymore after the changes introduced by P0135R1, as the expression f() is not an lvalue.

[expr.static.cast]/3:

An lvalue of type “cv1 T1” can be cast to type “rvalue reference to cv2 T2” if “cv2 T2” is reference-compatible with “cv1 T1” ([dcl.init.ref]). If the value is not a bit-field, the result refers to the object or the specified base class subobject thereof; otherwise, the lvalue-to-rvalue conversion ([conv.lval]) is applied to the bit-field and the resulting prvalue is used as the expression of the static_cast for the remainder of this section. If T2 is an inaccessible (Clause [class.access]) or ambiguous ([class.member.lookup]) base class of T1, a program that necessitates such a cast is ill-formed.

Then I looked at [expr.static.cast]/4. It just doesn't say anything about static_cast !! 

[expr.static.cast]/4:

An expression e can be explicitly converted to a type T if there is an implicit conversion sequence ([over.best.ics]) from e to T, or if overload resolution for a direct-initialization ([dcl.init]) of an object or reference of type T from e would find at least one viable function ([over.match.viable]). If T is a reference type, the effect is the same as performing the declaration and initialization

 T t(e);

for some invented temporary variable t ([dcl.init]) and then using the temporary variable as the result of the conversion. Otherwise, the result object is direct-initialized from e. [ Note: The conversion is ill-formed when attempting to convert an expression of class type to an inaccessible or ambiguous base class.  — end note ]


Now look at the changes proposed in P0135R1 for the two paragraphs:


Change in 5.2.9 [expr.static.cast] paragraph 3:

An glvalue, class prvalue, or array prvalue of type "cv1 T1" can be cast to type "rvalue reference to cv2 T2" if "cv2 T2" is reference-compatible with "cv1 T1" (8.5.3). If the value is not a bit-field, the result refers to the object or the specified base class subobject thereof; otherwise, the lvalue-to-rvalue conversion (4.1) is applied to the bit-field and the resulting prvalue is used as the expression of the static_cast for the remainder of this section. If T2 is an inaccessible (Clause 11) or ambiguous (10.2) base class of T1, a program that necessitates such a cast is ill-formed.
Drafting note: the deleted cases would also be handled by the immediately-following paragraph; we only need the special case above to convert lvalues to xvalues.

Change in 5.2.9 [expr.static.cast] paragraph 4:

An expression e can be explicitly converted to a type T using a static_cast of the form static_cast<T>(e) if the declaration T t(e); is well-formed, for some invented temporary variable t (8.5). The effect of such an explicit conversion If T is a reference type, the effect is the same as performing the declaration and initialization and then using the temporary variable as the result of the conversion. Otherwise, the result object is direct-initialized from e. The expression e is used as a glvalue if and only if the initialization uses it as a glvalue.


Somehow the changes stated above in paragraph 4 were not considered in the current version of C++1z. And more, the current wording in paragraph 4 doesn't seem to allow the alluded snippet to compile.

Richard Smith

unread,
Sep 18, 2016, 12:59:07 PM9/18/16
to std-dis...@isocpp.org
On 18 Sep 2016 7:39 am, "Belloc" <jabe...@gmail.com> wrote:
Consider the following snippet. It compiles in C++14 and AFAIK it should continue to compile after the changes introduced by P0135R1.

class A{};
A f
() { return A(); }
A
&& a = static_cast<A&&>(f());


Note that [expr.static.cast]/3 used to allow the conversion above, but not anymore after the changes introduced by P0135R1, as the expression f() is not an lvalue.

[expr.static.cast]/3:

An lvalue of type “cv1 T1” can be cast to type “rvalue reference to cv2 T2” if “cv2 T2” is reference-compatible with “cv1 T1” ([dcl.init.ref]). If the value is not a bit-field, the result refers to the object or the specified base class subobject thereof; otherwise, the lvalue-to-rvalue conversion ([conv.lval]) is applied to the bit-field and the resulting prvalue is used as the expression of the static_cast for the remainder of this section. If T2 is an inaccessible (Clause [class.access]) or ambiguous ([class.member.lookup]) base class of T1, a program that necessitates such a cast is ill-formed.

Then I looked at [expr.static.cast]/4. It just doesn't say anything about static_cast !! 

That's implied; all of this subclause is about static_cast.

[expr.static.cast]/4:

An expression e can be explicitly converted to a type T if there is an implicit conversion sequence ([over.best.ics]) from e to T, or if overload resolution for a direct-initialization ([dcl.init]) of an object or reference of type T from e would find at least one viable function ([over.match.viable]). If T is a reference type, the effect is the same as performing the declaration and initialization

 T t(e);

for some invented temporary variable t ([dcl.init]) and then using the temporary variable as the result of the conversion. Otherwise, the result object is direct-initialized from e. [ Note: The conversion is ill-formed when attempting to convert an expression of class type to an inaccessible or ambiguous base class.  — end note ]


Now look at the changes proposed in P0135R1 for the two paragraphs:


Change in 5.2.9 [expr.static.cast] paragraph 3:

An glvalue, class prvalue, or array prvalue of type "cv1 T1" can be cast to type "rvalue reference to cv2 T2" if "cv2 T2" is reference-compatible with "cv1 T1" (8.5.3). If the value is not a bit-field, the result refers to the object or the specified base class subobject thereof; otherwise, the lvalue-to-rvalue conversion (4.1) is applied to the bit-field and the resulting prvalue is used as the expression of the static_cast for the remainder of this section. If T2 is an inaccessible (Clause 11) or ambiguous (10.2) base class of T1, a program that necessitates such a cast is ill-formed.
Drafting note: the deleted cases would also be handled by the immediately-following paragraph; we only need the special case above to convert lvalues to xvalues.

Change in 5.2.9 [expr.static.cast] paragraph 4:

An expression e can be explicitly converted to a type T using a static_cast of the form static_cast<T>(e) if the declaration T t(e); is well-formed, for some invented temporary variable t (8.5). The effect of such an explicit conversion If T is a reference type, the effect is the same as performing the declaration and initialization and then using the temporary variable as the result of the conversion. Otherwise, the result object is direct-initialized from e. The expression e is used as a glvalue if and only if the initialization uses it as a glvalue.


Somehow the changes stated above in paragraph 4 were not considered in the current version of C++1z. And more, the current wording in paragraph 4 doesn't seem to allow the alluded snippet to compile.

--

---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussion+unsubscribe@isocpp.org.
To post to this group, send email to std-dis...@isocpp.org.
Visit this group at https://groups.google.com/a/isocpp.org/group/std-discussion/.

Belloc

unread,
Sep 18, 2016, 1:53:38 PM9/18/16
to ISO C++ Standard - Discussion


On Sunday, September 18, 2016 at 1:59:07 PM UTC-3, Richard Smith wrote:

That's implied; all of this subclause is about static_cast.


But the statement in P0135R1 is much clearer than the one in C++1z:

Richard Smith

unread,
Sep 18, 2016, 5:14:33 PM9/18/16
to std-dis...@isocpp.org
On Sun, Sep 18, 2016 at 10:53 AM, Belloc <jabe...@gmail.com> wrote:


On Sunday, September 18, 2016 at 1:59:07 PM UTC-3, Richard Smith wrote:

That's implied; all of this subclause is about static_cast.


But the statement in P0135R1 is much clearer than the one in C++1z:

The mention of static_cast here was removed by CWG issue 242. It seems clear enough to me from context that the rules given in [expr.static.cast] are rules for static_cast, and I'm not persuaded that clarity would be improved by repeating this in every paragraph.

That said, I think it would help if we used the same phrasing in every paragraph, and changed the first paragraph to tie that phrase to the validity of the static_cast. Right now, paragraphs 2 and 3 say "can be cast to", paragraphs 4, 6, 9, and 10 say "can be explicitly converted to", paragraph 7 says "can be performed explicitly using static_cast", and paragraphs 11, 12, and 13 say "can be converted to", and all cases mean the same thing (that this is a conversion that static_cast can perform).

Change in 5.2.9 [expr.static.cast] paragraph 4:
An expression e can be explicitly converted to a type T using a static_cast of the form static_cast<T>(e) if the declaration T t(e); is well-formed, for some invented temporary variable t (8.5). The effect of such an explicit conversion If T is a reference type, the effect is the same as performing the declaration and initialization and then using the temporary variable as the result of the conversion. Otherwise, the result object is direct-initialized from e. The expression e is used as a glvalue if and only if the initialization uses it as a glvalue.

--

Belloc

unread,
Sep 20, 2016, 5:36:38 PM9/20/16
to ISO C++ Standard - Discussion

On Sunday, September 18, 2016 at 6:14:33 PM UTC-3, Richard Smith wrote:
On Sun, Sep 18, 2016 at 10:53 AM, Belloc <jabe...@gmail.com> wrote:


On Sunday, September 18, 2016 at 1:59:07 PM UTC-3, Richard Smith wrote:

That's implied; all of this subclause is about static_cast.


But the statement in P0135R1 is much clearer than the one in C++1z:

The mention of static_cast here was removed by CWG issue 242. It seems clear enough to me from context that the rules given in [expr.static.cast] are rules for static_cast, and I'm not persuaded that clarity would be improved by repeating this in every paragraph.

I'm having a terrible problem understanding CWG issue 242 (emphasis is mine):

The meaning of an old-style cast is described in terms of const_caststatic_cast, and reinterpret_cast in 5.4 [expr.cast] paragraph 5. Ignoring const_cast for the moment, it basically says that if the conversion performed by a given old-style cast is one of those performed by static_cast, the conversion is interpreted as if it were a static_cast; otherwise, it's interpreted as if it were a reinterpret_cast, if possible. The following example is given in illustration:

    struct A {};
    struct I1 : A {};
    struct I2 : A {};
    struct D : I1, I2 {};
    A *foo( D *p ) {
	return (A*)( p ); // ill-formed static_cast interpretation
    }

The obvious intent here is that a derived-to-base pointer conversion is one of the conversions that can be performed using static_cast, so (A*)(p) is equivalent to static_cast<A*>(p), which is ill-formed because of the ambiguity.

Unfortunately, the description of static_cast in 5.2.9 [expr.static.cast] does NOT support this interpretation. The problem is in the way 5.2.9 [expr.static.cast] lists the kinds of casts that can be performed using static_cast. Rather than saying something like "All standard conversions can be performed using static_cast," it says

An expression e can be explicitly converted to a type T using a static_cast of the form static_cast<T>(e) if the declaration "T t(e);" is well-formed, for some invented temporary variable t.

Given the declarations above, the hypothetical declaration

    A* t(p); 
is NOT well-formed, because of the ambiguity. Therefore the old-style cast (A*)(p) is NOT one of the conversions that can be performed using static_cast, and (A*)(p) is equivalent to reinterpret_cast<A*>(p), which is well-formed under 5.2.10 [expr.reinterpret.cast] paragraph 7. 

So far so good. Then Mr. Miller makes his proposition to alter [expr.static.cast]/4 which is exactly the current wording for this paragraph in C++1z, as follows (emphasis is mine):

An expression e can be explicitly converted to a type T if there is an implicit conversion sequence (13.3.3.1) from e to T, or if overload resolution for a direct-initialization (8.6) of an object or reference of type T from e would find at least one viable function (13.3.2). If T is a reference type, the effect is the same as performing the declaration and initialization

T t(e);

for some invented temporary variable t (8.6) and then using the temporary variable as the result of the conversion. Otherwise, the result object is direct-initialized from e. [ Note: The conversion is ill-formed when attempting to convert an expression of class type to an inaccessible or ambiguous base class. —end note ] 

The only implicit conversion function that I can think of, that could apply to the desired conversion (A*)(p) in the example given by the OP, is the standard conversion in [conv.ptr]/3 which follows below (emphasis is mine):

A prvalue of type “pointer to cv D”, where D is a class type, can be converted to a prvalue of type “pointer to cv B”, where B is a base class (Clause 10) of D. If B is an inaccessible (Clause 11) or ambiguous (10.2) base class of D, a program that necessitates this conversion is ill-formed. The result of the conversion is a pointer to the base class subobject of the derived class object. The null pointer value is converted to the null pointer value of the destination type.

Well, both [expre.static.cast]/4 and [conv.ptr]/3 say that the conversion is ill-formed in case of ambiguity. The ambiguity was a problem with the previous wording in [expr.static.cast]/4 and, as far as I can tell, the problem didn't disappear with the new wording for the paragraph.

Richard Smith

unread,
Sep 22, 2016, 1:53:01 AM9/22/16
to std-dis...@isocpp.org
You're looking in the wrong place. The new wording says to look for an "implicit conversion sequence", and directs you to 13.3.3.1 where that term is defined. [conv.ptr] is talking about an "implicit conversion", which is a different kind of thing. See 13.3.3.1/2 for more about implicit conversion sequences:

"Implicit conversion sequences are concerned only with the type, cv-qualification, and value category of the argument and how these are converted to match the corresponding properties of the parameter. Other properties, such as the lifetime, storage class, alignment, accessibility of the argument, whether the argument is a bit-field, and whether a function is deleted (8.4.3), are ignored. So, although an implicit conversion sequence can be defined for a given argument-parameter pair, the conversion from the argument to the parameter might still be ill-formed in the final analysis."
 
(emphasis is mine):

A prvalue of type “pointer to cv D”, where D is a class type, can be converted to a prvalue of type “pointer to cv B”, where B is a base class (Clause 10) of D. If B is an inaccessible (Clause 11) or ambiguous (10.2) base class of D, a program that necessitates this conversion is ill-formed. The result of the conversion is a pointer to the base class subobject of the derived class object. The null pointer value is converted to the null pointer value of the destination type.

Well, both [expre.static.cast]/4 and [conv.ptr]/3 say that the conversion is ill-formed in case of ambiguity. The ambiguity was a problem with the previous wording in [expr.static.cast]/4 and, as far as I can tell, the problem didn't disappear with the new wording for the paragraph.

--

Belloc

unread,
Sep 22, 2016, 8:37:36 AM9/22/16
to ISO C++ Standard - Discussion


On Thursday, September 22, 2016 at 2:53:01 AM UTC-3, Richard Smith wrote:
 
You're looking in the wrong place. The new wording says to look for an "implicit conversion sequence", and directs you to 13.3.3.1 where that term is defined. [conv.ptr] is talking about an "implicit conversion", which is a different kind of thing. See 13.3.3.1/2 for more about implicit conversion sequences:


No, I'm not looking inthe wrong place.


1 An implicit conversion sequence is a sequence of conversions used to convert an argument in a function call to the type of the corresponding parameter of the function being called. The sequence of conversions is an implicit conversion as defined in Clause 4, which means it is governed by the rules for initialization of an object or reference by a single expression (8.6, 8.6.3).

2 Implicit conversion sequences are concerned only with the type, cv-qualification, and value category of the argument and how these are converted to match the corresponding properties of the parameter. Other properties, such as the lifetime, storage class, alignment, accessibility of the argument, whether the argument is a bit-field, and whether a function is deleted (8.4.3), are ignored. So, although an implicit conversion sequence can be defined for a given argument-parameter pair, the conversion from the argument to the parameter might still be ill-formed in the final analysis.

3 A well-formed implicit conversion sequence is one of the following forms:
(3.1) — a standard conversion sequence (13.3.3.1.1),
(3.2) — a user-defined conversion sequence (13.3.3.1.2), or
(3.3) — an ellipsis conversion sequence (13.3.3.1.3).

So, an implicit conversion can be presented in the form of a standard conversion sequence.


1 Table 11 summarizes the conversions defined in Clause 4 and partitions them into four disjoint categories:
Lvalue Transformation, Qualification Adjustment, Promotion, and Conversion. [ Note: These categories are
orthogonal with respect to value category, cv-qualification, and data representation: the Lvalue Transformations
do not change the cv-qualification or data representation of the type; the Qualification Adjustments do
not change the value category or data representation of the type; and the Promotions and Conversions do
not change the value category or cv-qualification of the type. —end note ]

2 [ Note: As described in Clause 4, a standard conversion sequence is either the Identity conversion by itself
(that is, no conversion) or consists of one to three conversions from the other four categories. If there
are two or more conversions in the sequence, the conversions are applied in the canonical order: Lvalue
Transformation, Promotion or Conversion, Qualification Adjustment. —end note ]

3 Each conversion in Table 11 also has an associated rank (Exact Match, Promotion, or Conversion). These are
used to rank standard conversion sequences (13.3.3.2). The rank of a conversion sequence is determined by
considering the rank of each conversion in the sequence and the rank of any reference binding (13.3.3.1.4). If
any of those has Conversion rank, the sequence has Conversion rank; otherwise, if any of those has Promotion
rank, the sequence has Promotion rank; otherwise, the sequence has Exact Match rank.

That is, a pointer conversion is a standard conversion sequence, which is an implicit conversion sequence.


1 A null pointer constant is an integer literal (2.13.2) with value zero or a prvalue of type std::nullptr_t. A null pointer constant can be converted to a pointer type; the result is the null pointer value of that type and is distinguishable from every other value of object pointer or function pointer type. Such a conversion is called a null pointer conversion. Two null pointer values of the same type shall compare equal. The conversion of a null pointer constant to a pointer to cv-qualified type is a single conversion, and not the sequence of a pointer conversion followed by a qualification conversion (4.5). A null pointer constant of integral type can be converted to a prvalue of type std::nullptr_t. [ Note: The resulting prvalue is not a null pointer value. —end note ]

2 A prvalue of type “pointer to cv T”, where T is an object type, can be converted to a prvalue of type “pointer to cv void”. The pointer value (3.9.2) is unchanged by this conversion.

3 A prvalue of type “pointer to cv D”, where D is a class type, can be converted to a prvalue of type “pointer to cv B”, where B is a base class (Clause 10) of D. If B is an inaccessible (Clause 11) or ambiguous (10.2) base class of D, a program that necessitates this conversion is ill-formed. The result of the conversion is a pointer to the base class subobject of the derived class object. The null pointer value is converted to the null pointer value of the destination type.

Finally, paragraph 3 above, as far as I know, describes the only implicit conversion sequence that could be used in the example given by the OP, to convert the lvalue p (after an lvalue-to-rvalue conversion), from a pointer to a derived class D, to a pointer to a class B. But the conversion is still ill-formed because of the ambiguity of the basis B.

 

Belloc

unread,
Sep 22, 2016, 10:24:33 AM9/22/16
to ISO C++ Standard - Discussion

On Thursday, September 22, 2016 at 2:53:01 AM UTC-3, Richard Smith wrote:

You're looking in the wrong place. The new wording says to look for an "implicit conversion sequence", and directs you to 13.3.3.1 where that term is defined. [conv.ptr] is talking about an "implicit conversion", which is a different kind of thing. See 13.3.3.1/2 for more about implicit conversion sequences:

Not only this, but Mr. Miller also wrote in CWG issue 242 the following:

  1. Change 5.2.9 [expr.static.cast] paragraph 2 as follows:

  2. An lvalue of type “cv1 B”, where B is a class type, can be cast to type “reference to cv2 D”, where D is a class derived (Clause 10 [class.derived]) from B, if a valid standard conversion from “pointer to D” to “pointer toB” exists (4.10 [conv.ptr]), cv2 is the same cv-qualification as, or greater cv-qualification than, cv1, and. If B is neither a virtual base class of D nor or a base class of a virtual base class of D, or if no valid standard conversion from “pointer to D” to “pointer to B” exists (4.10 [conv.ptr]), the program is ill-formedThe result has type “cv2 D”. An xvalue of type “cv1 B” may can be cast to type “rvalue reference to cv2 D” with the same constraints as for an lvalue of type “cv1 B”. If the object of type “cv1 B” is actually a subobject of an object of type D, the result refers to the enclosing object of type D. Otherwise, the behavior is undefined. [Example:...
  3. Change 5.2.9 [expr.static.cast] paragraph 4 as follows:

  4. An expression e can be explicitly converted to a type T using a static_cast of the form static_cast<T>(e) if the declaration T t(e); is well-formed, for some invented temporary variable t (8.5 [dcl.init]) there is an implicit conversion sequence (13.3.3.1 [over.best.ics]) from e to T, or if overload resolution for a direct-initialization (8.5 [dcl.init]) of an object or reference of type T from e would find at least one viable function (13.3.2 [over.match.viable]). The effect of such an explicit conversion is the same as performing the declaration and initialization

      T t(e);
    

    for some invented temporary variable t (8.5 [dcl.init]) and then using the temporary variable as the result of the conversion. [Note: The conversion is ill-formed when attempting to convert an expression of class type to an inaccessible or ambiguous base class. —end note] The expression e is used as a glvalue if and only if the initialization uses it as a glvalue.

Note that, in the second proposed change above, the OP writes: "[Note: the conversion is ill-formed when attempting to convert an expression of class type to an inaccessible or ambiguous base class - end note]" which is in complete disagreement with the reason that led him to write issue 242 in the first place, when he wrote: "The obvious intent here is that a derived-to-base pointer conversion is one of the conversions that can be performed using static_cast, so (A*)(p) is equivalent to static_cast<A*>(p), which is ill-formed because of the ambiguity.".

     

     

Reply all
Reply to author
Forward
0 new messages