You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[expr.const.cast] Use "shall" to impose the requirement CWG2828 #5355
For two similar types T1 and T2, a prvalue of type T1 may be explicitly converted to the type T2 using a const_cast if, considering the qualification-decompositions of both types, each P1i is the same as P2i for all i. The result of a const_cast refers to the original entity.
Presumably, the requirement in this rule should impose on any case that uses the const_cast casting. Even though the conversion would be a standard qualification conversion, it is not supported
typedefintCK[][2];
intmain(){
int arr[2][2];
CK& rf0 = arr; // ok, qualification conversion CK& rf = const_cast<CK&>(arr); // ill-formed, the requirement is not satisfied
}
Change [expr.const.cast] p3 to
For any casting from an expression of type T1 to type T2 using a const_cast, considering the qualification-decompositions of both types, each P1ishall be the same as P2i for all i. The result of a const_cast refers to the original entity.
The rules defined in the subsequence paragraphs that use const_cast casting all should satisfy this precondition.
Another issue appears in [expr.const.cast] p7: U1 suddenly appears without any introduction. Maybe, change it to
A conversion from a type T1 to a type T2 casts away constness if T1 and T2 are different, there is a qualification-decomposition ([conv.qual]) of T1
cv10 P10 cv11 P11 ... cv1n-1 P1n-1 cv1n U1
yielding n such that T2 has a qualification-decomposition of the form
cv20 P20 cv21 P21 ... cv2n-1 P2n-1 cv2n U2
and there is no qualification conversion that converts T1 to
For the first concern, note that [expr.cast] tries a number of casts in turn. Hitting "shall" = ill-formed too soon terminates the if ladder too soon.
It seems that const_cast can only convert the types if their corresponding Pi are the same. There seems to have no exception here. The types specified in P4 still should satisfy those requirements. Such as this example:
typedefintCK[][2];
int arr[2][2];
const_cast<CK&>(arr);
which is the case specified by [expr.const.cast] p4.1, where T1 is int [2][2] and T2 is int [][2].
The qualification-decompositions of them are: pointer to array of 2 array of 2 int vs. pointer to array of unknown bound of array of 2 int.
While that's true, it doesn't address the concern: We want "const_cast" simply to not be applicable and the ladder in [expr.cast] to go to the next step (e.g. static_cast). If we make the "const_cast" ill-formed before locking in to that choice, we'll never get to "static_cast" etc. in [expr.cast].
Alright. I forgot the existence of [expr.cast] p4. Since we mentioned [expr.cast] p4, I think out an example that is not well specified by [expr.cast] p4 but it may be another issue.
intconst p = 0;
(void*)&p;
a static_cast followed by a const_cast may be a matched interpretation. However, we didn't specify the temporary middle type in the conversion sequence, in other words, whether the conversion be interpreted to: const_cast<void*>(static_cast<void*>(&p)) or const_cast<void*>(static_cast<void const*>(&p)), the former is ill-formed since static_cast casts away constness. I remember I saw this issue somewhere, I will cite the question here if I find it.
Yes, the implementation needs to pick a suitable middle type. "shall not cast away constness" is probably the wrong phrasing, then. It should be "cannot cast away constness", it seems.
structB0{
int b0;
};
structB1{
int b1;
};
structD:B0,B1{
};
structTrick{
Trick() = default;
Trick(std::any){
}
template<classT>
operatorT(){
return T{0};
}
};
intmain(){
D const* Dptr = new D;
B1* ptr = (B1*)Dptr; // #1
}
For the cast notation conversion that occurs at #1, the options can be these
Sane options
const_cast<D*>(Dptr); // a expected resultconst_cast<B1*>(static_cast<B1const*>(Dptr)); // a expected resultconst_cast<D*>(static_cast<D const*>(Dptr)); // a possible option, the standard conversion can apply to the resultconst_cast<B1*>(reinterpret_cast<B1const*>(Dptr)); // the result is the same as the value of Dptr
Insane options
static_cast<Trick>(Dptr); // insane but it is true since we didn't specify the middle type
Maybe, there are some other weird options. And in the sane options group, there are two static_cast followed by a const_cast, the program can be ill-formed as long as the implementation prefers. Furthermore, how to interpret the conversion relies on what the middle type the implementations would like. what about an insane implementation that selects the Trick as a middle type? That means, static_cast<Trick>(Dptr); is the first option in the list.
Incidentally
If a conversion can be interpreted in more than one way as a static_cast followed by a const_cast
This is unclear how more than one interpretation intends? Does it mean there is more than one interpretation in the situation where the middle type should be the same or arbitrary middle types?
If a conversion can be interpreted in more than one of the ways listed above, the interpretation that appears first in the list is used, even if a cast resulting from that interpretation is ill-formed.
Thus, the list is an ordered list: if a single const_cast suffices, we'll take that. So, we pick the const_cast<D*>(Dptr) interpretation.
The sentence doesn't talk about middle types, so it's for any middle type.
The sentence doesn't talk about middle types, so it's for any middle type.
Only when the middle type is D* will const_cast<D*>(Dptr) be firstly picked. Why wouldn't the implementation picks Trick as the middle type and thus static_cast<Trick>(Dptr); is the first picked one?
Or, do you mean for a given set S of possible middle types, which may be { Trick, D*, ...}, const_cast<D*>(Dptr) first appears in the list, so it is picked by implementation?
However, it still cannot interpret the program will be ill-formed since there is more than one way as a static_cast followed by a const_cast for that given set S. const_cast<B1*>(static_cast<B1 const*>(Dptr)); and const_cast<D*>(static_cast<D const*>(Dptr)); Should we change that to
If a conversion can be interpreted in more than one way as a static_cast followed by a const_cast that is used, the conversion is ill-formed.
which implies that a static_cast followed by a const_cast first appears in the list for the given set S.
For the first (const_cast) option, there is no middle type. Anyway, my interpretation is that you try the first option, iterating through all possibilities if necessary. If you can form a conversion ("can be converted"), you pick that conversion (which might end up being ill-formed nonetheless). For your example, we never look at the second and further options.
Because, although the (middle) type is required to similar to type-id such that const_cast can be done, however, we have many cases that the type of the operand in a cast-expression is notsimilar to type-id. For example:
int const* const* is not similar to char* const* const* at all. Or, consider the inheritance case
structB{};
structD:B{};
D const d;
(B*) &d;
the type D const* is neither similar to B* nor B const*. I think [expr.const.cast] p7 has converted what we want here. In short, without cv and P ugly strings,
the (middle) type T3 should be similar to type-id and conversion from T1 to T3 does not cast away constness.
And we can give the creative space to implementations as long as they chose a type T4 such that T3 can be converted to T4 with a qualification conversion or T4 is just T3.
I was trying to suggest that any middle type employed in the sequence of casts be similar to type-id.
Yes, I understand that the type of the operand is likely not similar to anything.
I was trying to suggest that any middle type employed in the sequence of casts be similar to type-id.
Ah, I misunderstood what you said in the above comment. Yes, you're totally right, any (middle) type(s) used in the conversion should be similar to type-id(the destination type), and to not cast away constness have been required in each subclause except [expr.const.cast]. So, we only require that any middle type and type-id are similar is good enough.
For your example #5355 (comment) , I think there is a fundamental misunderstanding: We can't rely on any implicit conversions on the result of the static_cast or const_cast. And the operand of a const_cast must be a type similar to the target type of the const_cast.
I'm wondering if this issue can be resolved by simply saying that the last cast in each sequence in [expr.cast] p4 uses the type denoted by type-id as its target type.
Oh, and I would be interested in seeing an example for the "ambiguous static_cast / const_cast" case where an earlier case in the list is actually selected, with the understanding of the target type presented in the previous comment. If there is such a case, the linked pull request should be pursued.
I'm wondering if this issue can be resolved by simply saying that the last cast in each sequence in [expr.cast] p4 uses the type denoted by type-id as its target type.
Yes, I think this is necessary to add to the pull request to avoid a misunderstanding similar to #5355 (comment)
After the restriction to say the type of the last conversion should be the type-id, then for the concern mentioned in #5355 (comment), I'm wondering whether it is necessary to emphasize say the employed type of the first conversion in the conversion sequence formed by static_cast followed by const_cast or reinterpret_cast followed by a const_cast should be similar to type-id or not. the second conversion formed by const_cast can only process two similar types. If static_cast followed by const_cast or reinterpret_cast followed by a const_cast is used, this implies that the employed type in the middle conversion must be a similar type to type-id(the type that be restricted in the last conversion), otherwise, such a conversion cannot be formed.
Oh, and I would be interested in seeing an example for the "ambiguous static_cast / const_cast" case where an earlier case in the list is actually selected, with the understanding of the target type presented in the previous comment.
int**** ptr = 0;
auto t = (intconst*const*const*const*)ptr;
From 2 to 5, they all be a static_cast followed by a const_cast, hence they are ambiguous, however, option 1 is an earlier case in the list, regardless of how many interpretations of a static_cast followed by a const_cast can have, it does not matter since the options that follow 1are not used.
changed the title [-][expr.const.cast] Use "shall" to impose the requirement[/-][+][expr.const.cast] Use "shall" to impose the requirement CWG2828[/+]on Nov 22, 2023
Activity
jensmaurer commentedon Mar 21, 2022
For the first concern, note that [expr.cast] tries a number of casts in turn. Hitting "shall" = ill-formed too soon terminates the if ladder too soon.
xmh0511 commentedon Mar 21, 2022
It seems that const_cast can only convert the types if their corresponding Pi are the same. There seems to have no exception here. The types specified in P4 still should satisfy those requirements. Such as this example:
which is the case specified by [expr.const.cast] p4.1, where
T1isint [2][2]andT2isint [][2].The qualification-decompositions of them are:
pointer to array of 2 array of 2 intvs.pointer to array of unknown bound of array of 2 int.where P11 is not the same as P21.
jensmaurer commentedon Mar 21, 2022
While that's true, it doesn't address the concern: We want "const_cast" simply to not be applicable and the ladder in [expr.cast] to go to the next step (e.g. static_cast). If we make the "const_cast" ill-formed before locking in to that choice, we'll never get to "static_cast" etc. in [expr.cast].
xmh0511 commentedon Mar 21, 2022
How would
static_castbe used in a const_cast conversion? I cannot figure out that example. Would you specify that?jensmaurer commentedon Mar 21, 2022
Take this example:
[expr.cast] first attempts a
const_castfromint*tovoid *. But that fails, so it attempts astatic_castnext. That succeeds, and all is well.If, however, we say the attempt to form the
const_castis ill-formed, we'll never reach thestatic_castattempt.xmh0511 commentedon Mar 21, 2022
Alright. I forgot the existence of [expr.cast] p4. Since we mentioned [expr.cast] p4, I think out an example that is not well specified by [expr.cast] p4 but it may be another issue.
a static_cast followed by a const_castmay be a matched interpretation. However, we didn't specify the temporary middle type in the conversion sequence, in other words, whether the conversion be interpreted to:const_cast<void*>(static_cast<void*>(&p))orconst_cast<void*>(static_cast<void const*>(&p)), the former is ill-formed sincestatic_cast castsaway constness. I remember I saw this issue somewhere, I will cite the question here if I find it.jensmaurer commentedon Mar 21, 2022
Yes, the implementation needs to pick a suitable middle type. "shall not cast away constness" is probably the wrong phrasing, then. It should be "cannot cast away constness", it seems.
xmh0511 commentedon Mar 21, 2022
@jensmaurer we may specify the middle type by replacing the
Uin the qualification-decomposition of the source type. This issue sources from https://stackoverflow.com/questions/39216316/does-the-c-specification-say-how-types-are-chosen-in-the-static-cast-const-casjensmaurer commentedon Mar 21, 2022
We don't need to specify the middle type; if there is any, the cast works. The implementation can figure that out.
xmh0511 commentedon Mar 22, 2022
Does it mean the result is implementation-defined? Since only implementation clearly knows how the conversion chain works.
jensmaurer commentedon Mar 22, 2022
No. The underlying assumption is that the end result is the same, regardless of which middle type was chosen. Do you have a counterexample?
xmh0511 commentedon Mar 22, 2022
Consider this example:
For the cast notation conversion that occurs at
#1, the options can be theseMaybe, there are some other weird options. And in the sane options group, there are two
static_cast followed by a const_cast, the program can be ill-formed as long as the implementation prefers. Furthermore, how to interpret the conversion relies on what the middle type the implementations would like. what about an insane implementation that selects theTrickas a middle type? That means,static_cast<Trick>(Dptr);is the first option in the list.Incidentally
This is unclear how more than one interpretation intends? Does it mean there is more than one interpretation in the situation where the middle type should be the same or arbitrary middle types?
jensmaurer commentedon Mar 22, 2022
Thus, the list is an ordered list: if a single
const_castsuffices, we'll take that. So, we pick theconst_cast<D*>(Dptr)interpretation.The sentence doesn't talk about middle types, so it's for any middle type.
xmh0511 commentedon Mar 22, 2022
Only when the middle type is
D*will const_cast<D*>(Dptr) be firstly picked. Why wouldn't the implementation picksTrickas the middle type and thusstatic_cast<Trick>(Dptr);is the first picked one?Or, do you mean for a given set
Sof possible middle types, which may be{ Trick, D*, ...},const_cast<D*>(Dptr)first appears in the list, so it is picked by implementation?However, it still cannot interpret the program will be ill-formed since there is more than one way as a static_cast followed by a const_cast for that given set
S.const_cast<B1*>(static_cast<B1 const*>(Dptr));andconst_cast<D*>(static_cast<D const*>(Dptr));Should we change that towhich implies that
a static_cast followed by a const_castfirst appears in the list for the given setS.jensmaurer commentedon Mar 22, 2022
For the first (
const_cast) option, there is no middle type. Anyway, my interpretation is that you try the first option, iterating through all possibilities if necessary. If you can form a conversion ("can be converted"), you pick that conversion (which might end up being ill-formed nonetheless). For your example, we never look at the second and further options.7 remaining items
xmh0511 commentedon Mar 22, 2022
Instead of giving too much free room for implementation, It is better to minimum limit the types used in the conversion if possible.
It seems that we just want any (middle) type used in
static_castorreinterpret_castfollowed by aconst_castwith the type as the following:jensmaurer commentedon Mar 22, 2022
I'd really like to limit the cv and P ugly strings to a single subclause. Why is requiring "similar" not good enough?
xmh0511 commentedon Mar 23, 2022
Because, although the (middle) type is required to similar to type-id such that
const_castcan be done, however, we have many cases that the type of the operand in a cast-expression is not similar to type-id. For example:int const* const*is not similar tochar* const* const*at all. Or, consider the inheritance casethe type
D const*is neither similar toB*norB const*. I think [expr.const.cast] p7 has converted what we want here. In short, without cv and P ugly strings,jensmaurer commentedon Mar 23, 2022
I was trying to suggest that any middle type employed in the sequence of casts be similar to type-id.
Yes, I understand that the type of the operand is likely not similar to anything.
xmh0511 commentedon Mar 23, 2022
Ah, I misunderstood what you said in the above comment. Yes, you're totally right, any (middle) type(s) used in the conversion should be similar to type-id(the destination type), and to not cast away constness have been required in each subclause except [expr.const.cast]. So, we only require that any middle type and type-id are similar is good enough.
jensmaurer commentedon Aug 24, 2022
For your example #5355 (comment) , I think there is a fundamental misunderstanding: We can't rely on any implicit conversions on the result of the static_cast or const_cast. And the operand of a const_cast must be a type similar to the target type of the const_cast.
I'm wondering if this issue can be resolved by simply saying that the last cast in each sequence in [expr.cast] p4 uses the type denoted by type-id as its target type.
jensmaurer commentedon Aug 24, 2022
Oh, and I would be interested in seeing an example for the "ambiguous static_cast / const_cast" case where an earlier case in the list is actually selected, with the understanding of the target type presented in the previous comment. If there is such a case, the linked pull request should be pursued.
xmh0511 commentedon Aug 25, 2022
Yes, I think this is necessary to add to the pull request to avoid a misunderstanding similar to #5355 (comment)
After the restriction to say the type of the last conversion should be the type-id, then for the concern mentioned in #5355 (comment), I'm wondering whether it is necessary to emphasize say the employed type of the first conversion in the conversion sequence formed by
static_cast followed by const_castorreinterpret_cast followed by a const_castshould be similar to type-id or not. the second conversion formed byconst_castcan only process two similar types. Ifstatic_cast followed by const_castorreinterpret_cast followed by a const_castis used, this implies that the employed type in the middle conversion must be a similar type to type-id(the type that be restricted in the last conversion), otherwise, such a conversion cannot be formed.this can be interpreted as
From 2 to 5, they all be a static_cast followed by a const_cast, hence they are ambiguous, however, option
1is an earlier case in the list, regardless of how many interpretations of a static_cast followed by a const_cast can have, it does not matter since the options that follow1are not used.jensmaurer commentedon Aug 27, 2022
Thanks.
jensmaurer commentedon Nov 22, 2023
CWG2828
[-][expr.const.cast] Use "shall" to impose the requirement[/-][+][expr.const.cast] Use "shall" to impose the requirement CWG2828[/+]frederick-vs-ja commentedon Apr 28, 2024
Should have been fixed by #6906 (0e026f5).