27

GCC and Clang both reject the C-style cast in the following code.

http://coliru.stacked-crooked.com/a/c6fb8797d9d96a27

struct S {
    typedef const int* P;
    operator P() { return nullptr; }
};
int main() {
    int* p1 = const_cast<int*>(static_cast<const int*>(S{}));
    int* p2 = (int*)(S{});
}
main.cpp: In function 'int main()':
main.cpp:7:25: error: invalid cast from type 'S' to type 'int*'
     int* p2 = (int*)(S{});
main.cpp:7:15: error: cannot cast from type 'S' to pointer type 'int *'
    int* p2 = (int*)(S{});
              ^~~~~~~~~~~

However, according to the standard, a C-style cast can perform the conversions performed by a static_cast followed by a const_cast. Is this code well-formed? If not, why not?

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
Brian Bi
  • 111,498
  • 10
  • 176
  • 312
  • Does it work with 2 c-style casts ie `(int*)(const int*)(S{})` ? – Richard Critten Aug 29 '16 at 21:35
  • @RichardCritten yes, but that's not the question ;) – Brian Bi Aug 29 '16 at 21:40
  • C++11 §5.4 about the cast notation does not mention any limitation of not invoking user defined conversions. On the contrary §5.4/3 states that "Any type conversion not mentioned below and not explicitly defined by the user (12.3) is ill-formed.", which explicitly drags in the possibility of invoking a user-defined conversion. – Cheers and hth. - Alf Aug 29 '16 at 21:49
  • 1
    I've been chasing around the spec for a while now trying to figure this one out. The spec is pretty unclear about how it determines what types it uses when trying to do the `static_cast` and the `const_cast`. If it tries to do them in the way that you've described, it absolutely would work, but if it tries to just do `const_cast(static_cast(S{})))`, then it will definitely fail. I have no idea how the compiler would know to try to add `const` here, but the spec doesn't seem to say anything on the subject... – templatetypedef Aug 29 '16 at 22:39
  • Seems pretty clear to me: [expr.cast]/4.3 says that this should work, so the compilers are bugged. – M.M Aug 29 '16 at 22:47
  • 1
    @templatetypedef obviously the "static_cast followed by const_cast" case will use different types for the two cases, since one will be const-qualified and one not – M.M Aug 29 '16 at 22:48
  • 1
    @M.M Well that's the thing - the spec doesn't actually say that the compiler should attempt to add `const` anywhere. It just says "try a `static_cast` and then a `const_cast`," not noting how to choose which type to use for the `static_cast`. – templatetypedef Aug 29 '16 at 22:50
  • @M.M I mean, I agree with you that it *should* do that, but I don't see anywhere in the spec that explains what specific choices it should try, which is weird given how detailed it is everywhere else. – templatetypedef Aug 29 '16 at 22:51
  • @templatetypedef that's entirely up to the compiler to figure out. The spec doesn't need to provide any more detail. It even gives an example where conversion fails due to there not being a unique sequence, which suggests it is intended that the compiler try out all possible sequences (in a similar way to other conversion situations). Maybe a compiler vendor would say it's too hard to implement and the spec should specify a narrower set of possibliities, IDK. – M.M Aug 29 '16 at 22:54
  • @template reading it, I think it does not specify what type to use for the static cast. It says if you can do the conversion with *a static cast* it must be done, however. The compiler must prove that *no type* in the static cast, followed by *no type* in the const cast, to convert to the target type, before eliminating that option. In practice, this problem is actually easy (in some cases? Hmm, 10 pointers?), but the standard just makes the demand and does not specify the details. Or what mm said. – Yakk - Adam Nevraumont Aug 29 '16 at 23:00
  • 1
    @Yakk perhaps Brian's case is invalid because `static_cast` followed by const_cast is possible, but so is `static_cast` followed by a const_cast therefore it is ambiguous. But I think this suggests the standard wording is defective as templatetypedef suggests – M.M Aug 29 '16 at 23:02
  • `int* p1 = const_cast(static_cast(S{}));` does that cause an issue? Multiple different legal `static_cast`s? – Yakk - Adam Nevraumont Aug 29 '16 at 23:02
  • @m.m damn you. 30 seconds this time. But "If a conversion can be interpreted in more than one way as a static_cast followed by a const_cast, the conversion is ill-formed." -- I think that is a sufficient justification to make this illegal under the (current draft) standard. And maybe not a sufficient justification for it to actually *be* illegal. Wonder if a there is a way to check if that is the actual reason it is rejected? Or if that makes other seemingly acceptable cases with no user defined operator equally illegal, yet supported? – Yakk - Adam Nevraumont Aug 29 '16 at 23:03
  • @Yakk http://coliru.stacked-crooked.com/a/363feac2631bafce – Brian Bi Aug 29 '16 at 23:12
  • @brian aha compiler bug! :) (well standard bug really) – Yakk - Adam Nevraumont Aug 29 '16 at 23:14
  • 2
    The extra const seems meaningless, but regardless one can always cast to `const volatile` and then `const_cast` both away. – T.C. Aug 29 '16 at 23:45
  • 1
    @t.c. meaningless, but the standard says if there is more than one valid `static_cast` followed by a `const_cast` the cast is ambiguous (assuming the c-style cast gets that far). Unless there is some wording that the top-level `const` or `volatile` on a static cast is "meaninless" or the like... This is pretty stupid, no? – Yakk - Adam Nevraumont Aug 30 '16 at 00:23

1 Answers1

10

This is core issue 909:

According to 5.4 [expr.cast] paragraph 4, one possible interpretation of an old-style cast is as a static_cast followed by a const_cast. One would therefore expect that the expressions marked #1 and #2 in the following example would have the same validity and meaning:

struct S {
  operator const int* ();
};

void f(S& s)  {
  const_cast<int*>(static_cast<const int*>(s));  // #1
  (int*) s;  // #2
}

However, a number of implementations issue an error on #2.

Is the intent that (T*)x should be interpreted as something like

const_cast<T*>(static_cast<const volatile T*>(x))

Rationale (July, 2009):

According to the straightforward interpretation of the wording, the example should work. This appears to be just a compiler bug.

This was apparently never resolved by neither Clang nor GCC. Time to open tickets.

Columbo
  • 60,038
  • 8
  • 155
  • 203