Подтвердить что ты не робот

Как предотвратить получение класса шаблона более одного раза?

У меня есть следующий класс шаблонов:

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 может быть адаптирован для достижения такого поведения (в то время как классы A, B, U, V не могут); однако T не должен иметь никаких знаний о производных классах A, B, C.

Есть ли способ предотвратить получение такого класса шаблонов более одного раза? Идеально выдавать ошибку компиляции в таком случае или, по крайней мере, ошибку компоновщика.

4b9b3361

Ответ 1

Это возможно, если базовый класс 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). Для этого важны функции друзей. Создайте вспомогательный класс, не являющийся членом, в зависимости от <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. (Возможно, вам удастся объявить все производные классы в заголовке и не беспокоиться об обстреле assert.)

Демо.


Вы отредактировали, чтобы сказать, что T не может знать свои производные типы. Ну, вы ничего не можете сделать во время компиляции, так как эта информация просто недоступна. Если T является полиморфным, вы можете наблюдать, что динамический тип является производным классом A или B, но не в конструкторе или деструкторе. Если есть какая-то другая функция, надежно вызываемая производным классом, вы можете зацепиться за это:

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.
    }
};

Ответ 2

Я не большой поклонник макросов, но если использование макросов не проблема для вас, вы можете использовать простое и компактное решение следующим образом:

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

Ответ 3

Так как вы упоминаете, что можно пройти A, B и C до 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
}

Ответ 4

Единственный ответ, который я могу себе представить, - иметь реестр экземпляров и их производных. Затем вы можете создать метафайл, который выполняет поиск в реестре. Вы передаете ему базу и производную, и если вы не получаете зарегистрированный тип, он возвращает тип void или что-то еще, поэтому он вызывает ошибку компилятора. Основываясь на ваших требованиях, что база не знает о полученном, это единственный возможный ответ, который я могу придумать.

Ваши объявления будут выглядеть примерно так:

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

Я верю, что вы столкнетесь с множеством вопросов, которые трудно решить даже здесь - удачи!

И помните, шаблонные метапрограммы являются чисто функциональными. Вы не можете "добавить" в реестр любым удобным способом. Вам нужно будет определить его один раз, чтобы удержать все. С положительной стороны, если кто-то забывает добавлять в реестр, они будут знать, или если они используют ранее определенный... на минусе, это уродливо как утка.