Repository navigation
[expr.const.cast] Use "shall" to impose the requirement CWG2828 #5355
Description
Activity
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.
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:
typedef int CK[][2]; int arr[2][2]; const_cast<CK&>(arr);
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.
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].
we'll never get to "static_cast" etc. in [expr.cast].
How would
static_castbe used in a const_cast conversion? I cannot figure out that example. Would you specify that?Take this example:
int p; (void *)&p;[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.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.
int const p = 0; (void*)&p;
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.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.
@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-casWe don't need to specify the middle type; if there is any, the cast works. The implementation can figure that out.
We don't need to specify the middle type; if there is any, the cast works. The implementation can figure that out.
Does it mean the result is implementation-defined? Since only implementation clearly knows how the conversion chain works.
Does it mean the result is implementation-defined?
No. The underlying assumption is that the end result is the same, regardless of which middle type was chosen. Do you have a counterexample?
Do you have a counterexample?
Consider this example:
struct B0{ int b0; }; struct B1{ int b1; }; struct D:B0,B1{ }; struct Trick{ Trick() = default; Trick(std::any){ } template<class T> operator T(){ return T{0}; } }; int main(){ D const* Dptr = new D; B1* ptr = (B1*)Dptr; // #1 }
For the cast notation conversion that occurs at
#1, the options can be theseSane options
const_cast<D*>(Dptr); // a expected result const_cast<B1*>(static_cast<B1 const*>(Dptr)); // a expected result const_cast<D*>(static_cast<D const*>(Dptr)); // a possible option, the standard conversion can apply to the result const_cast<B1*>(reinterpret_cast<B1 const*>(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 theTrickas 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_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.
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 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 toIf 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_castfirst appears in the list for the given setS.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
Instead of giving too much free room for implementation, It is better to minimum limit the types used in the conversion if possible.
we want all the involved casts to convert to a type similar to the type given as the type-id of the cast-expression
It seems that we just want any (middle) type used in
static_castorreinterpret_castfollowed by aconst_castwith the type as the following:If The qualification-decomposition of the type
T1of the operand in thecast-expressioniscv10 P10 cv11 P11 ... cv1n-1 P1n-1 cv1n U1
yielding
n(where n≥1 ) and the qualification-decomposition of the typeT2of the type-id in thecast-expressioniscv20 P20 cv21 P21 ... cv2m-1 P2m-1 cv2m U2
yielding m (where m≥1) such that a type
T3iscv30 P20 cv31 P21 ... cv3m-1 P2m-1 cv3m U2
Conversion from
T1toT3does not cast away constnessAn implementation is permitted to chose a type
T4provide that a prvalue ofT3can be converted toT4with a qualification conversion.I'd really like to limit the cv and P ugly strings to a single subclause. Why is requiring "similar" not good enough?
Why is requiring "similar" not good enough?
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* ptr = 0; (char* const* const*)ptr;
int const* const*is not similar tochar* const* const*at all. Or, consider the inheritance casestruct B{}; struct D:B{}; D const d; (B*) &d;
the 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,the (middle) type
T3should be similar to type-id and conversion fromT1toT3does not cast away constness.And we can give the creative space to implementations as long as they chose a type
T4such thatT3can be converted toT4with a qualification conversion orT4is justT3.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_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.
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 = (int const*const*const*const*)ptr;
this can be interpreted as
- static_cast<int const * const * const * const * >(ptr);
- const_cast<int const * const * const * const * >(static_cast<int * * * const * >(ptr));
- const_cast<int const * const * const * const * >(static_cast<int * * const * const * >(ptr));
- const_cast<int const * const * const * const * >(static_cast<int * const * const * const * >(ptr));
- const_cast<int const * const * const * const *>(static_cast<int const * 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
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.Thanks.
- 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 - addedcwgIssue must be reviewed by CWG.Issue must be reviewed by CWG.not-editorialIssue is not deemed editorial; the editorial issue is kept open for tracking.Issue is not deemed editorial; the editorial issue is kept open for tracking.
on Nov 22, 2023
[expr.const.cast] p3 states
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
Change [expr.const.cast] p3 to
The rules defined in the subsequence paragraphs that use
const_castcasting all should satisfy this precondition.Another issue appears in [expr.const.cast] p7:
U1suddenly appears without any introduction. Maybe, change it towould be more clear.