Topic: Safety of functional-style casts


Author: ademkin@earthlink.net (Andrew Demkin)
Date: Thu, 20 Dec 2001 00:06:25 GMT
Raw View
Greetings,

Sections 5.2.3 [expr.type.conv] and 5.4 [expr.cast] of the standard suggest
that old-style and functional-style casts should be interpreted, in order,
as one of the following conversions:

- a static_cast (5.2.9),
- a static_cast followed by a const_cast,
- a reinterpret_cast (5.2.10), or
- a reinterpret_cast followed by a const_cast

If more than one conversion applies, the interpretation that appears first
in this list is the one that's chosen, even if it's ill-formed.

However, Section 5.4.9 goes on to say that for pointers to incomplete class
types, even if there is an inheritance relationship between the source and
destination classes, whether static_cast or reinterpret_cast is used is
unspecified.

Now, given the following:

   struct A{};
   struct B{};

   typedef B* BPtr;

   A a;

   BPtr p1 = (BPtr) &a;
   BPtr p2 = BPtr(&a);
   BPtr p3 = static_cast<BPtr>(&a);

...I'd expect the final three statements to generate a compiler error
because struct B is not derived from struct A. In the two compilers I've
tested, only the explicit static_cast expression results in any diagnostic.


The way I read the standard, a compiler should only have latitude to use
reinterpret_cast if either struct A and/or struct B is an incomplete type.
I'm wondering whether I'm misinterpreting the standard or whether these
compilers are non-conforming.

For compatibility reasons with C, I can understand the desire to interpret
an  old-style cast as a reinterpret_cast. But, if the compiler is free to
use reinterpret_cast for functional-style casts as well, then the
definitions of bind1st<> and bind2nd<> (20.3.6.2/4) are possibly unsafe for
pointer argument types.

For example, the compilers I've tested fail to detect the type error below
in the 2nd call to for_each():

   #include <algorithm>
   #include <functional>
   #include <vector>

   using namespace std;

   struct A{};
   struct B{};

   struct Func : binary_function<const A*, int, void>
   {
       void operator()(const A*, int) const;
   };

   void UnsafeBind()
   {
      A a;
      B b;
      Func f;
      vector<int> v;

      for_each(v.begin(), v.end(), bind1st(f, &a));
      for_each(v.begin(), v.end(), bind1st(f, &b)); // woops!
   }

The intent of the language must be to catch sort of error, no?


Thanks,
Andrew.



---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.research.att.com/~austern/csc/faq.html                ]





Author: Martin von Loewis <loewis@informatik.hu-berlin.de>
Date: Thu, 20 Dec 2001 18:46:41 GMT
Raw View
ademkin@earthlink.net (Andrew Demkin) writes:

> The way I read the standard, a compiler should only have latitude to use
> reinterpret_cast if either struct A and/or struct B is an incomplete type.
> I'm wondering whether I'm misinterpreting the standard or whether these
> compilers are non-conforming.

You are misinterpreting it. In your example, the explicit type
notation "cannot be interpreted" as a static_cast, thus it is a
reinterpret_cast.

Presumably, you where thinking that

  BPtr p1 = (BPtr) &a;

"can be interpreted" as

  BPtr p1 = static_cast<BPtr>(&a);

but is ill-formed, thus the explicit type conversion is ill-formed.

I'd admit that this looks like a defect, for not elaborating "can be
interpreted": it apparently means something else than "is
well-formed".  The common interpretation is that the only case that is
ruled out (i.e. where it can be interpreted as a static case that is
ill-formed) is the case of ambiguity, as explained in the next
sentence.

>       for_each(v.begin(), v.end(), bind1st(f, &a));
>       for_each(v.begin(), v.end(), bind1st(f, &b)); // woops!
>    }
>
> The intent of the language must be to catch sort of error, no?

That sounds like a defect in bind1st to me, for not mandating a
static_cast of the value argument.

Regards,
Martin

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.research.att.com/~austern/csc/faq.html                ]





Author: ademkin@earthlink.net (Andrew Demkin)
Date: Fri, 21 Dec 2001 13:17:25 CST
Raw View
In article <j4itb2brsq.fsf@informatik.hu-berlin.de>, Martin von Loewis
<loewis@informatik.hu-berlin.de> wrote:

> You are misinterpreting it. In your example, the explicit type
> notation "cannot be interpreted" as a static_cast, thus it is a
> reinterpret_cast.

Martin, thanks for the clarification.


> I'd admit that this looks like a defect, for not elaborating "can be
> interpreted": it apparently means something else than "is
> well-formed".  The common interpretation is that the only case that is
> ruled out (i.e. where it can be interpreted as a static case that is
> ill-formed) is the case of ambiguity, as explained in the next
> sentence.
>
> >       for_each(v.begin(), v.end(), bind1st(f, &a));
> >       for_each(v.begin(), v.end(), bind1st(f, &b)); // woops!
> >    }
> >
> > The intent of the language must be to catch sort of error, no?
>
> That sounds like a defect in bind1st to me, for not mandating a
> static_cast of the value argument.

I too think this is a defect. What I'm still trying to decipher, however,
is whether it's best to report something against bind1st/2nd, or against
explicit type conversion (5.2.3).

Disclaimer: I don't have a copy of the ARM close-by, and I've only had a
brief look at C99, but what escapes me is why a functional-style
conversion like "BPtr(&a)" allows a reinterpret_cast which requires no
diagnostic. Since the same syntax "T(x)" is used for object construction,
which normally involves static type-checking, I'm trying to reconcile this
inconsistency.

My recollection of C is that it does not support functional-style
conversions -- only casts of the form "(T) x" -- so I would not expect
legacy C to influence the current semantics. Of course, if this is a
compatibility issue, I'll accept it as unfortunate and note a defect in
bind1st/2nd.

-- Andrew.

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.research.att.com/~austern/csc/faq.html                ]