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

前端 未结 4 1982
礼貌的吻别
礼貌的吻别 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: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 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 , 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.
        }
    };
    

提交回复
热议问题