Reputation: 1831
I have some code that behaves like this:
class Base {};
class MyClass : public Base {};
MyClass* BaseToMyClass(Base* p)
{
MyClass* pRes = dynamic_cast<MyClass*>(p);
assert(pRes);
return pRes;
}
Is there a way to add a compile time check so I can catch calls to this function where p is not an instance of MyClass? I had a look at Alexandrescu's SUPERSUBCLASS function but I'm not sure if it can do the job.
Thanks!
Upvotes: 1
Views: 2545
Reputation: 224159
Is there a way to add a compile time check so I can catch calls to this function where p is not an instance of MyClass?
Generally, if you want to check this at compile-time, you'd take the derived class as an argument.
However, if the only thing you have is a Base*
or a Base&
, then you cannot know whether it refers to a MyClass
object. It's the very nature of run-time polymorphism that this is to be found out at run-time. This check can only be done where the MyClass
object/reference/pointer is converted to a Base*
/Base&
. That's why dynamic_cast<>()
was invented.
Your function basically is a safe_cast
. If you put it into the right syntax, it looks like this:
template< typename Derived, typename Base >
inline Derived* safe_cast(Base* pb)
{
#if defined _NDEBUG
return static_cast<Derived*>(pb);
#else
Derived* pd = dynamic_cast<Derived*>(pb);
assert(pd);
return pd;
#endif
}
template< typename Derived, typename Base >
inline Derived& safe_cast(Base& rb)
{
return *safe_cast<Derived*>(&rb);
}
Upvotes: 4
Reputation: 523654
(I'm posting this as a different answer since the strategy is different.)
You could use a static assert for a more flexible constraint, e.g.
#include <boost/type_traits.hpp>
#include <boost/static_assert.hpp>
template <class T>
MyClass* BaseToMyClass(T* x) {
BOOST_STATIC_ASSERT(!(boost::is_convertible<T, MyClass>::value));
// ^ This ensures T is not MyClass at compile time.
MyClass* p = dynamic_cast<MyClass*>(x);
assert(p);
return p;
}
The error message looks like
x.cpp: In function 'MyClass* BaseToMyClass(T*) [with T = MyClass]':
x.cpp:26:22: instantiated from here
x.cpp:13:1: error: invalid application of 'sizeof' to incomplete type 'boost::STATIC_ASSERTION_FAILURE<false>'
Note that, it also cannot detect if you declare a pointer as Base* p = new MyClass
.
This code uses Boost.StaticAssert and Boost.TypeTraits. The former can be replaced by the built-in static_assert
in C++0x, while the latter is available as a built-in library since TR1.
Upvotes: 0
Reputation: 523654
Try this:
struct Base { virtual ~Base() {} };
struct MyClass : public Base {};
template <class T> MyClass* BaseToMyClass(T p) {
int x[-(int)sizeof(T)];
return 0;
}
template <> MyClass* BaseToMyClass(Base* p) {
printf("1\n");
MyClass* pRes = dynamic_cast<MyClass*>(p);
assert(pRes != NULL);
return pRes;
}
If the function is called with Base*
, the 1st specialization will be used. If it is called with MyClass*
, the generic version will be used. Since an array cannot have negative size, this will cause a compile-time error, which can be traced back to the point of instantiation.
The sizeof(T)
is to ensure the error is shown only at instantiation time.
Note that this method is not suitable if Base
has any other subclasses you want to pass.
Example usage:
int main () { // 22
MyClass p; // 23
Base q; // 24
BaseToMyClass(&q); // 25
BaseToMyClass(&p); // 26
} // 27
Results in
x.cpp: In function 'MyClass* BaseToMyClass(T) [with T = MyClass*]':
x.cpp:26:22: instantiated from here
x.cpp:11:27: error: creating array with negative size ('-0x00000000000000008')
Note that it cannot detect what the pointer is new
-ed with. For instance, in
int main () {
Base* p = new Base;
Base* q = new MyClass;
BaseToMyClass(p);
BaseToMyClass(q);
delete p;
delete q;
}
the program will be compiled successfully.
Upvotes: 1