I have the following template class:
template<class I>
class T : public I
{
// ...
};
This template class need to be derived once (and only once) for a given template parameter I
.
class A : public T<U> {}; // ok
class B : public T<V> {}; // ok
class C : public T<U> {}; // compile error
Template class T
can be adapted to achieve such a behavior (while classes A
, B
, U
, V
cannot); however, T
must not have any knowledge about derived classes A
, B
, C
.
Is there any way to prevent such a template class from being derived more than once? Ideally issuing a compilation error in such a case or, at least, a linker error.
There are ways to restrict the types you can use inside a template you write by using specific typedefs inside your template. This will ensure that the compilation of the template specialisation for a type that does not include that particular typedef will fail, so you can selectively support/not support certain types.
Can there be more than one argument to templates? Yes, like normal parameters, we can pass more than one data type as arguments to templates.
Deriving from a non-template base classIt is quite possible to have a template class inherit from a 'normal' class. This mechanism is recommended if your template class has a lot of non-template attributes and operations. Instead of putting them in the template class, put them into a non-template base class.
An individual class defines how a group of objects can be constructed, while a class template defines how a group of classes can be generated. Note the distinction between the terms class template and template class: Class template. is a template used to generate template classes.
This is possible if the base class T
knows the types of its derived classes. This knowledge can be passed by CRTP or by an overloading tag to its constructor. Here's the latter case:
template<class I>
class T : public I
{
protected:
template< class Derived >
T( Derived * ) {
static_assert ( std::is_base_of< T, Derived >::value,
"Be honest and pass the derived this to T::T." );
Then, T::T( Derived * )
needs to do something that will cause a problem if it has two specializations (with different Derived
). Friend functions are great for that. Instantiate an auxiliary, non-member class depending on <T, Derived>
, with a friend
function that depends on T
but not Derived
.
T_Derived_reservation< T, Derived >{};
}
};
Here's the auxiliary class. (Its definition should come before T
.) First, it needs a base class to allow ADL on T_Derived_reservation< T, Derived >
to find a signature that doesn't mention Derived
.
template< typename T >
class T_reservation {
protected:
// Make the friend visible to the derived class by ADL.
friend void reserve_compile_time( T_reservation );
// Double-check at runtime to catch conflicts between TUs.
void reserve_runtime( std::type_info const & derived ) {
#ifndef NDEBUG
static std::type_info const & proper_derived = derived;
assert ( derived == proper_derived &&
"Illegal inheritance from T." );
#endif
}
};
template< typename T, typename Derived >
struct T_Derived_reservation
: T_reservation< T > {
T_Derived_reservation() {
reserve_compile_time( * this );
this->reserve_runtime( typeid( Derived ) );
}
/* Conflicting derived classes within the translation unit
will cause a multiple-definition error on reserve_compile_time. */
friend void reserve_compile_time( T_reservation< T > ) {}
};
It would be nice to get a link error when two .cpp
files declare different incompatible derived classes, but I can't prevent the linker from merging the inline functions. So, the assert
will fire instead. (You can probably manage to declare all the derived classes in a header, and not worry about the assert
firing.)
Demo.
You've edited to say that T
cannot know its derived types. Well, there's nothing you can do at compile time, since that information is simply unavailable. If T
is polymorphic, then you can observe the dynamic type to be the derived class A
or B
, but not in the constructor or destructor. If there's some other function reliably called by the derived class, you can hook into that:
template< typename I >
class T {
protected:
virtual ~ T() = default;
something_essential() {
#ifndef NDEBUG
static auto const & derived_type = typeid( * this );
assert ( derived_type == typeid( * this ) &&
"Illegal inheritance from T." );
#endif
// Do actual essential work.
}
};
I'm not a big fan of macros but if using macros is not a problem to you - you could use a simple and compact solution as follows:
#include <iostream>
template <class>
struct prohibit_double_inheritance { };
#define INHERIT(DERIVING, BASE) \
template<> struct prohibit_double_inheritance<BASE> { };\
struct DERIVING: BASE
template<class I>
struct T: I
{
// ...
static void do_something() {
std::cout << "hurray hurray!" << std::endl;
}
};
struct U { };
struct V { };
INHERIT(A, T<U>) {
};
//INHERIT(B, T<U>) { // cause redetinition of the struct
//}; // prohibit_double_inheritance<T<U>>
int main() {
A::do_something();
}
[live demo]
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With