-
Notifications
You must be signed in to change notification settings - Fork 772
[expr.const.cast] Use "shall" to impose the requirement CWG2828 #5355
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
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 The qualification-decompositions of them are: 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]. |
How would |
Take this example:
[expr.cast] first attempts a If, however, we say the attempt to form the |
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;
|
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 |
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. |
No. The underlying assumption is that the end result is the same, regardless of which middle type was chosen. 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
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
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 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? |
Thus, the list is an ordered list: if a single The sentence doesn't talk about middle types, so it's for any middle type. |
Only when the middle type is Or, do you mean for a given set 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
which implies that |
For the first ( |
How about this suggestion?
Although the first |
So, the net concern is that the relationship of the two sentences after the bullets in [expr.cast] p4 is unclear. |
Yes. I think the change is reasonable. If there is more than one of the same kind of interpretations, the program is ill-formed because of the ambiguity(which one is selected). However, if there is an unambiguity option that first appears in the list and thus is used, it is regardless of whether the number of the interpretation that appears in the below is, it does not matter. |
@jensmaurer I figure out a simple counterexample struct Trick{
Trick() = default;
template<class T>
Trick(T){}
template<class T>
operator T(){
return T{0};
}
};
int main(){
int a = 0;
char* ptr = (char*)&a
} Anyway, |
I think we want all the involved casts to convert to a type similar to the type given as the type-id of the cast-expression. And we want the last conversion in each bullet to convert to the type-id. |
Yes. That's the reason why I want to ask for some restrictions/requirements to the possible used types in the cast notation conversion. And it is exactly why I think the result may be implementation-defined If we do not specify some requirements |
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
|
I'd really like to limit the cv and P ugly strings to a single subclause. Why is requiring "similar" not good enough? |
Because, although the (middle) type is required to similar to type-id such that int const* const* ptr = 0;
(char* const* const*)ptr;
struct B{};
struct D:B{};
D const d;
(B*) &d; the type
|
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. |
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
int**** ptr = 0;
auto t = (int const*const*const*const*)ptr; 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 |
Thanks. |
[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_cast
casting all should satisfy this precondition.Another issue appears in [expr.const.cast] p7:
U1
suddenly appears without any introduction. Maybe, change it towould be more clear.
The text was updated successfully, but these errors were encountered: