Velocity Reviews - Computer Hardware Reviews

Velocity Reviews > Newsgroups > Programming > C++ > Why does this incorrect CRTP static_cast compile?

Reply
Thread Tools

Why does this incorrect CRTP static_cast compile?

 
 
kfrank29.c@gmail.com
Guest
Posts: n/a
 
      04-25-2013
Hello Group!

The Wikipedia CRTP article:

http://en.wikipedia.org/wiki/Curious...mplate_pattern

has a comment that confused me:

Pitfalls

One issue with CRTP is that the correct usage of the
pattern by derived classes cannot be checked at compile
time. For example, with the above example of Shape_CRTP,
if the definition of Circle were changed to:

class Circle: public Shape_CRTP<Square> {};

it would still compile with no errors. However, running
code that performs clone() on a Circle object will lead
to undefined behavior.

(By the way, Mark's thread, "enforce access to derived class
via pointer to base," got me thinking about CRTP and led me
to the Wikipedia article.)

I've analyzed why I think the Wikipedia example should compile,
but I'm really not sure I understand what's going on.

(For context and details, see the article.)

I've put together a stripped-down example that illustrates
the main points of my analysis.

The core issue is that code within the template CRTP base
class looks as if it's making a compile-time illegal
static_cast when instantiated incorrectly, and I'm trying
to figure out why it's compile-time legal.

(I am a little foggy on what, exactly, static_cast is and
in not allowed to do, and I find the language in the
standard difficult to follow.)

Here is my example, followed by my analysis:


==========


class B { // base class for example
};

template <typename T> class CR : public B { // template class for CRTP
public:
void f() { // doesn't matter if this is virtual
// why does this compile when instantiated with F?
T *pt = static_cast<T*>(this);
}
};

class E : public CR<E> { // normal CRTP idiom
};

class F : public CR<E> { // error -- should derive from CR<F>
};

class X : public B { // to illustrate bad static_cast without CRTP
};

class Y : public B { // to illustrate bad static_cast without CRTP
};

int main (int argc, char *argv[]) {
E e;
e.f(); // okay -- static cast from base* to derived*

F f;
f.f(); // why doesn't this cause bad static_cast to be instantiated?

X x;

// error: invalid static_cast from type 'X*' to type 'Y*'
// Y *py = static_cast<Y*>(&x);

// compile-time legal static_cast from X* to B* to Y*
Y *py = static_cast<Y*>(static_cast<B*>(&x));
}


==========


(Note, CR derives from B only to mirror more closely the
Wikipedia example. I have the same analysis and confusion
without it.)

I think the point is that whether or not the function
CR<T>::f is virtual, the compile-time static type of
this in the code for f is CR<T>* for whatever T is when
CR is instantiated, even though at run time the type of
this is actually E* or F* (classes derived from CR).

The line e.f() causes the member function CR<E>::f() to be
instantiated. This causes the static cast from type CR<E>*
(the static type of this) to E* to be instantiated. This
should be fine, and is part of the standard CRTP idiom.

If the static type of this were E* for E derived from
CR<E>, then we would not even need the static cast, but
we do.

The line f.f() causes CR<F>::f() to be instantiated.
The way I analyze this, we still have a static_cast from
CR<E>* (still the static type of this) to E* -- still okay,
because E derives from CR<E> (even though we are dealing
with an F).

For an F, the dynamic type of this is F*. If the static
type of this were also F* for F derived from CR<E>, then
the static_cast wouldn't be enough to get from F* to E*.

I've tried to draw an analogy with using static_cast with
the complication of the CRTP stripped away.

At the end of the example code, X and Y both derive from
B. We can't static_cast from X* to Y* because neither does
X derive from Y nor vice versa. But we can do a two-step
static_cast from X* to B* to Y* (legal at compile time,
but undefined behavior at run time), because X and Y
derive from a common base class.

I believe that this is analogous to instantiating F::f().
this for F is of type F*, but the static type of this in
the code for f in CR<T> (when instantiated as CR<E>) is
CR<E>*. This is analogous to a static_cast of F* to its
pointer-to-base, CR<E>*. We then static_cast to E*, which
is compile-time legal (but undefined behavior at run time)
because E also derives from CR<E>.

To belabor the point, the two-step conversion:

F* --> CR<E>* --> E*

is analogous to the previous:

X* --> B* --> Y*

because E and F have CR<E> as their common base class,
and conversion from F* to E* is compile-time legal /
run-time undefined for the same reason that X* --> Y*
is.

There are a lot of steps in this chain of reasoning, and
I'm uncertain of the details. Any corrections or
clarifications would be very welcome.


Thanks.


K. Frank
 
Reply With Quote
 
 
 
 
Öö Tiib
Guest
Posts: n/a
 
      04-25-2013
On Thursday, 25 April 2013 06:18:27 UTC+3, (E-Mail Removed) wrote:
> The Wikipedia CRTP article:
>
> http://en.wikipedia.org/wiki/Curious...mplate_pattern
>
> has a comment that confused me:
>
> Pitfalls
>
> One issue with CRTP is that the correct usage of the
> pattern by derived classes cannot be checked at compile
> time. For example, with the above example of Shape_CRTP,
> if the definition of Circle were changed to:
>
> class Circle: public Shape_CRTP<Square> {};
>
> it would still compile with no errors. However, running
> code that performs clone() on a Circle object will lead
> to undefined behavior.


The comment is correct, you get '*this' that is of type
'Circle const' cast into 'Square const&':

template <typename Derived>
class Shape_CRTP : public Shape {
public:
virtual Shape *clone() const {
return new Derived(static_cast<Derived const&>(*this));
}
};

It is illegal static_cast with undefined results. It is simple to get
rid of undefined behavior here by using dynamic_cast like that:

template <typename Derived>
class Shape_CRTP : public Shape {
public:
virtual Shape *clone() const {
return new Derived( dynamic_cast<Derived const&>( *this ) );
}
};


If that 'clone' is called for 'class Circle: public Shape_CRTP<Square> {};'
then std::bad_cast exception will be thrown runtime by the 'dynamic_cast'
and it is well-defined what will happen with it.

'static_cast' has been used in Wikipedia article because for some reason
people keep using cheaper but unsafe C++ features without any actual
performance problems.
 
Reply With Quote
 
 
 
 
kfrank29.c@gmail.com
Guest
Posts: n/a
 
      04-25-2013
Hi Öö!

On Thursday, April 25, 2013 5:10:00 AM UTC-4, Öö Tiib wrote:
> On Thursday, 25 April 2013 06:18:27 UTC+3, (E-Mail Removed) wrote:
>
> > The Wikipedia CRTP article:
> > http://en.wikipedia.org/wiki/Curious...mplate_pattern
> > has a comment that confused me:
> >
> > Pitfalls
> >
> > One issue with CRTP is that the correct usage of the
> > pattern by derived classes cannot be checked at compile
> > time. For example, with the above example of Shape_CRTP,
> > if the definition of Circle were changed to:
> >
> > class Circle: public Shape_CRTP<Square> {};
> >
> > it would still compile with no errors. However, running
> > code that performs clone() on a Circle object will lead
> > to undefined behavior.

>
> The comment is correct, you get '*this' that is of type
> 'Circle const' cast into 'Square const&':


I admit that I am confused by all of this.

But I don't think your comment is correct, at least in
terms of what happens at compile time.

I think is this specific case where Circle has been incorrectly
instantiated as "class Circle: public Shape_CRTP<Square> {};"
the code is performing a static_cast that converts '*this',
of static type 'Shape_CRTP<Square> const' to a reference of
type 'Square const&', a (compile-time) legal use of static_cast,
because Square derives from Shape_CRTP<Square>.

If in fact, at compile time, "you get '*this' that is of type
'Circle const' cast into 'Square const&'", you should get a
compiler error, because neither Circle nor Square derives from
one another, so you can't use static_cast. The fact that they
derive from a common base class isn't enough.

At least that's how I understand it.

> template <typename Derived>
> class Shape_CRTP : public Shape {
> public:
> virtual Shape *clone() const {
> return new Derived(static_cast<Derived const&>(*this));
> }
> };
>
> It is illegal static_cast with undefined results.


I do agree that at run time it's undefined behavior (because
neither Circle nor Square derives from the other.)

But I believe that the Shape_CRTP code you quote is fully
legal, and it's only the instantiated code that becomes
become run-time illegal when you incorrectly derive Circle
from Shape_CRTP<Square>.

What's odd -- but I believe true -- is that the incorrect
instantiated code is compile-time legal, and only becomes
illegal / undefined at run time.

> It is simple to get
> rid of undefined behavior here by using dynamic_cast like that:
>
> template <typename Derived>
> class Shape_CRTP : public Shape {
> public:
> virtual Shape *clone() const {
> return new Derived( dynamic_cast<Derived const&>( *this ) );
> }
> };
>
> If that 'clone' is called for 'class Circle: public Shape_CRTP<Square> {};'
> then std::bad_cast exception will be thrown runtime by the 'dynamic_cast'
> and it is well-defined what will happen with it.


Agreed.

But, as a side note, as I understand it, CRTP is often used
in order to avoid the modest overhead of "polymorphic" base
classes (i.e., classes with virtual functions that participate
in RTTI).

In the Wikipedia example we are discussing, we have the virtual
clone and destructor, so RTTI / dynamic_cast is available.
But in other CRTP uses (e.g., the example code I gave), the
classes are not "polymorphic," so I don't think dynamic_cast
would be available.

> 'static_cast' has been used in Wikipedia article because for some reason
> people keep using cheaper but unsafe C++ features without any actual
> performance problems.


I agree that in the "Polymorphic copy construction"
Wikipedia example dynamic_cast would be better / safer.

But -- and please correct me if I am wrong -- in the
"Static polymorphism" example, the base class has no
virtual functions so RTTI, and hence dynamic_cast, can't
be used.

I am still quite uncertain about all of this.

I can talk myself into believing that I understand the
CRTP, but every time I look at it, I get confused again.


Again, thanks for your comments, and any additional
wisdom.


K. Frank
 
Reply With Quote
 
 
 
Reply

Thread Tools

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off
Trackbacks are On
Pingbacks are On
Refbacks are Off


Similar Threads
Thread Thread Starter Forum Replies Last Post
Really throwing this out there - does anyone have a copy of my oldDancer web browser? steven.miale@gmail.com Python 1 04-10-2013 03:32 PM
findcontrol("PlaceHolderPrice") why why why why why why why why why why why Mr. SweatyFinger ASP .Net 2 12-02-2006 03:46 PM
Is static_cast<int>(static_cast<double>(a)) == a? Bo Peng C++ 11 10-20-2006 12:59 PM
HSSI & cRTP Arthur Lashin Cisco 0 03-19-2005 08:19 AM
ABC vs. CRTP? Mike Smith C++ 7 03-03-2005 05:43 PM



Advertisments