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

前端 未结 4 1938
礼貌的吻别
礼貌的吻别 2021-02-05 07:47

I have the following template class:

template
class T : public I
{
    // ...
};

This template class need to be derived once (an

相关标签:
4条回答
  • 2021-02-05 08:21

    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]

    0 讨论(0)
  • 2021-02-05 08:22

    Since you mention that it's okay to pass A, B and C to T then how about this runtime solution?

    #include <cassert>
    #include <typeinfo>
    
    //This class will check to see each T<X> is only instantiated with a unique Y
    template <class X>
    struct T_helper
    {
        template <class Y>
        static void check()
        {
            if(derived_type)
                assert(*derived_type == typeid(Y));
            else
                derived_type = &typeid(Y);
        }
        static const std::type_info * derived_type;
    };
    
    template <class X>
    const std::type_info * T_helper<X>::derived_type = nullptr;
    
    template <class X, class Y>
    struct T
    {
        T()
        {
            T_helper<X>::template check<Y>();
        }
    };
    
    struct A : T<int, A> {};
    struct B : T<int, B> {};
    
    int main()
    {
        A a1, a2, a3; // These are all okay
        B b1;         // This one will trigger the assert
    }
    
    0 讨论(0)
  • 2021-02-05 08:37

    The only answer I can envision is to have a registry of instantiations and their derivatives. You could then devise a metafunction that searches the registry. You pass it the base and the derived and if you're not deriving with the registered type it returns void type or something so it causes a compiler error. Based on your requirements that the base has no knowledge of the derived this is the only remotely possible answer I can think of.

    Your declarations would then look something like:

    struct A : search_registry<T<U>, A>::type { ... };
    

    I believe you're going to run into many issues that will be tough to solve even here--good luck!

    And remember, template metaprograms are purely functional. You can't "add" to the registry in any nice way. You're going to need to define it once to hold everything. On the plus side if someone forgets to add to the registry they'll know, or if they use a previously defined one...on the minus, this is ugly as duck.

    0 讨论(0)
  • 2021-02-05 08:42

    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.
        }
    };
    
    0 讨论(0)
提交回复
热议问题