Why does this incorrect CRTP static_cast compile?

Discussion in 'C++' started by kfrank29.c@gmail.com, Apr 25, 2013.

  1. Guest

    Hello Group!

    The Wikipedia CRTP article:

    http://en.wikipedia.org/wiki/Curiously_recurring_template_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
    , Apr 25, 2013
    #1
    1. Advertising

  2. Öö Tiib Guest

    On Thursday, 25 April 2013 06:18:27 UTC+3, wrote:
    > The Wikipedia CRTP article:
    >
    > http://en.wikipedia.org/wiki/Curiously_recurring_template_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.
    Öö Tiib, Apr 25, 2013
    #2
    1. Advertising

  3. Guest

    Hi Öö!

    On Thursday, April 25, 2013 5:10:00 AM UTC-4, Öö Tiib wrote:
    > On Thursday, 25 April 2013 06:18:27 UTC+3, wrote:
    >
    > > The Wikipedia CRTP article:
    > > http://en.wikipedia.org/wiki/Curiously_recurring_template_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
    , Apr 25, 2013
    #3
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Mike Smith

    ABC vs. CRTP?

    Mike Smith, Mar 2, 2005, in forum: C++
    Replies:
    7
    Views:
    898
    Dietmar Kuehl
    Mar 3, 2005
  2. Mr. SweatyFinger
    Replies:
    2
    Views:
    1,762
    Smokey Grindel
    Dec 2, 2006
  3. Bo Peng
    Replies:
    11
    Views:
    1,069
    Victor Bazarov
    Oct 20, 2006
  4. Nagaraj
    Replies:
    1
    Views:
    840
    Lionel B
    Mar 1, 2007
  5. junyangzou
    Replies:
    13
    Views:
    247
Loading...

Share This Page