Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to prevent a template class from being derived more than once?

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.

like image 928
shrike Avatar asked Jan 13 '17 17:01

shrike


People also ask

How will you restrict the template for a specific datatype?

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 arguments to templates?

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.

Can you derive template classes?

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.

What is the difference between class template and template 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.


2 Answers

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.
    }
};
like image 65
Potatoswatter Avatar answered Oct 04 '22 12:10

Potatoswatter


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]

like image 38
W.F. Avatar answered Oct 04 '22 13:10

W.F.