I cannot think about a proper question title to describe the problem. Hopefully the details below explains my problem clear.
Consider the following code
If you have C++11 available, you can use static_assert
(if not, I am sure you can emulate these things with boost). You could assert for e.g. is_convertible<Derived*,Base*>
or is_base_of<Base,Derived>
.
This all takes place in Base, and all it ever has is the information of Derived. It will never have a chance to see if the calling context is from a D2 or D1, as this makes no difference, since Base<D1>
is instantiated once, in one specific way, no matter whether it was instantiated by D1 or D2 deriving from it (or by the user explicitly instantiating it).
Since you do not want to (understandably, since it has sometimes significant runtime cost and memory overhead) use dynamic_cast, try to use something often called "poly cast" (boost has its own variant too):
template<class R, class T>
R poly_cast( T& t )
{
#ifndef NDEBUG
(void)dynamic_cast<R>(t);
#endif
return static_cast<R>(t);
}
This way in your debug/test builds the error gets detected. While not a 100% guarantee, in practice this often catches all the mistakes people make.
If you can't count with C++11, you could try this trick:
Add a static function in Base
that returns a pointer to its specialied type:
static Derived *derived( ) { return NULL; }
Add a static check
function template to base that takes a pointer:
template< typename T > static bool check( T *derived_this ) { return ( derived_this == Base< Derived >::derived( ) ); }
In your Dn
constructors, call check( this )
:
check( this )
Now if you try to compile:
$ g++ -Wall check_inherit.cpp -o check_inherit
check_inherit.cpp: In instantiation of ‘static bool Base<Derived>::check(T*) [with T = D2; Derived = D1]’:
check_inherit.cpp:46:16: required from here
check_inherit.cpp:19:62: error: comparison between distinct pointer types ‘D2*’ and ‘D1*’ lacks a cast
check_inherit.cpp: In static member function ‘static bool Base<Derived>::check(T*) [with T = D2; Derived = D1]’:
check_inherit.cpp:20:5: warning: control reaches end of non-void function [-Wreturn-type]
In general I do not think there is a way to get this, which should not be considered outright ugly and reverts to the usage of evil features. Here is a summary of what would work and what would not.
Use of static_assert
(either from C++11 or from boost) does not work, because a check in the definition of Base
can only use the types Base<Derived>
and Derived
. So the following will look good, but fail:
template <typename Derived>
class Base
{
public :
void call ()
{
static_assert( sizeof( Derived ) != 0 && std::is_base_of< Base< Derived >, Derived >::value, "Missuse of CRTP" );
static_cast<Derived *>(this)->call_impl();
}
};
In case you try to declare D2
as class D2 : Base< D1 >
the static assert will not catch this, since D1
actualy is derived from Base< D1 >
and the static assert is completely valid. If you however derive from Base< D3 >
where D3
is any class not deriving from Base< D3 >
both the static_assert
as well as the static_cast
will trigger compilation errors, so this is absolutely useless.
Since the type D2
you would need to check in the code of Base
is never passed to the template the only way to use static_assert
would be to move it after the declarations of D2
which would require the same person who implemented D2
to check, which again is useless.
One way to get around this would be by adding a macro, but this would beget nothing but pure ugliness:
#define MAKE_DISPATCHABLE_BEGIN( DeRiVeD ) \
class DeRiVeD : Base< DeRiVed > {
#define MAKE_DISPATCHABLE_END( DeRiVeD )
}; \
static_assert( is_base_of< Base< Derived >, Derived >::value, "Error" );
This only gains ugliness, and the static_assert
is again superflous, because the template makes sure that the types alway match. So no gain here.
dynamic_cast
which was explicitely meant for this scenario. If you need this more often it would probably make sense to implement your own asserted_cast
(there is an article on Dr. Jobbs on this), which automatically triggers a failed assert when the dynamic_cast
fails. General point: Templates are not protected from being instantiated with wrong params. This is well known issue. It is not recommended to spend time in trying to fix this. The number or ways how templates can be abused is endless. In your particular case you might invent something. Later on you will modify your code and new ways of abusing will show up.
I know that C++11 has static assert that might help. I do not know full details.
Other point. Besides compiling errors there is static analysis. What you are asking for has some something with this. Analysis does not necessarily look for security flaws. It can ensure that there is no recusrion in the code. It can check that there is no derivatives from some class, you can pose restrictions on params of templates and functions, etc. This is all analysis. Such widely varying constrains cannot be supported by the compiler. I am not sure that this is the right way to go, just telling about this possibility.
p.s. Our company provides services in this area.
There is no way to prevent the user from writing incorrect derived classes; however, there are ways to prevent your code from invoking classes with unexpected hierarchies. If there are points at which the user is passing Derived
to the library functions, consider having those library functions perform a static_cast
to the expected derived type. For example:
template < typename Derived >
void safe_call( Derived& t )
{
static_cast< Base< Derived >& >( t ).call();
}
Or if there are multiple levels of hierarchy, consider the following:
template < typename Derived,
typename BaseArg >
void safe_call_helper( Derived& d,
Base< BaseArg >& b )
{
// Verify that Derived does inherit from BaseArg.
static_cast< BaseArg& >( d ).call();
}
template < typename T >
void safe_call( T& t )
{
safe_call_helper( t, t );
}
In both of thse cases, safe_call( d1 )
will compile while safe_call( d2 )
will fail to compile. The compiler error may not be as explicit as one would like for the user, so it may be worthwhile to consider static asserts.
1) make all constructors of Base private (if there are no constructors, add one)
2) declare Derived template parameter as friend of Base
template <class Derived>
class Base
{
private:
Base(){}; // prevent undesirable inheritance making ctor private
friend Derived; // allow inheritance for Derived
public :
void call ()
{
static_cast<Derived *>(this)->call_impl();
}
};
After this it would be impossible to create any instances of the wrong inherited D2.