如何防止模板类被多次派生?

时间:2017-01-13 17:08:22

标签: c++ templates inheritance

我有以下模板类:

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

对于给定的模板参数I,此模板类需要一次(且仅一次)派生。

class A : public T<U>  {};    // ok
class B : public T<V>  {};    // ok
class C : public T<U>  {};    // compile error

可以调整模板类T以实现此类行为(而类ABUV不能);但是,T必须对衍生类ABC一无所知。

有没有办法防止这样的模板类被多次派生?理想情况下,在这种情况下发出编译错误,或者至少是链接器错误。

4 个答案:

答案 0 :(得分:17)

如果基类T知道其派生类的类型,则可以这样做。这些知识可以通过CRTP或重载标记传递给它的构造函数。这是后一种情况:

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." );

然后,T::T( Derived * )需要做一些会导致问题的事情,如果它有两个特化(具有不同的Derived)。朋友的功能非常棒。根据{{​​1}}实例化辅助非成员类,其<T, Derived>函数取决于friend,但不取决于T

Derived

这是辅助类。 (它的定义应该在 T_Derived_reservation< T, Derived >{}; } }; 之前。)首先,它需要一个基类来允许T上的ADL找到一个不提及T_Derived_reservation< T, Derived >的签名。

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 > ) {} }; 文件声明不同的不兼容派生类时,获取链接错误会很好,但我无法阻止链接器合并内联函数。因此,.cpp会触发。 (您可以设法在标头中声明所有派生类,而不用担心assert触发。)

Demo

您已编辑过说assert无法知道其派生类型。好吧,在编译时你无能为力,因为这些信息根本不可用。如果T是多态的,那么您可以将动态类型视为派生类TA,但不能在构造函数或析构函数中观察。如果派生类可靠地调用了其他一些函数,你可以勾选:

B

答案 1 :(得分:5)

我不是宏的忠实粉丝,但如果使用宏对你来说不是问题 - 你可以使用一个简单紧凑的解决方案,如下所示:

#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();
}

link

答案 2 :(得分:3)

既然你提到可以将ABC传递给T那么这个运行时解决方案怎么样?

#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
}

答案 3 :(得分:3)

我能想到的唯一答案是拥有实例化及其衍生物的注册表。然后,您可以设计一个搜索注册表的元函数。你传递它的基础和派生,如果你没有使用注册类型派生它返回void类型或其他东西,所以它会导致编译器错误。根据您的要求,基地不知道派生的这是我能想到的唯一可能的答案。

您的声明将如下所示:

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

我相信你会遇到许多难以解决的问题 - 祝你好运!祝你好运!

请记住,模板元程序纯粹是功能性的。你不能添加&#34;以任何好方式到注册表。您需要定义一次以容纳所有内容。从好的方面来说,如果有人忘记添加到他们已经知道的注册表中,或者如果他们使用之前定义的那个......在减号上,那就像鸭子那样丑陋。