Re: Why does this incorrect CRTP static_cast compile?
Hi =D6=F6!
On Thursday, April 25, 2013 5:10:00 AM UTC-4, =D6=F6 Tiib wrote:
On Thursday, 25 April 2013 06:18:27 UTC+3, kfran...@gmail.com 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