Как принудительно использовать любопытно повторяющийся шаблон шаблона в C ++

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

template<typename T>
class Base {
  public:
    void do_something() {
    }
};

Он предназначен для использования в качестве любопытно повторяющегося шаблона шаблона. Он должен быть унаследован как class B : public Base<B>. Он не должен наследоваться, как class B : public Base<SomeoneElse>. Я хочу статически закрепить это требование. Если кто-то использует это неправильно, я ожидаю ошибки на этапе компиляции.

Я просто вставляю static_cast<T const&>(*this) в do_something(). Таким образом, класс, наследующий шаблон, является или наследуется от класса, указанного в качестве параметра шаблона. Извините за запутанное выражение. Говоря простым языком, он требует B is или наследуется от SomeoneElse в class B : public Base<SomeoneElse>.

Я не знаю, является ли это оптимальным способом добиться этого. Мне это кажется отвратительным.

Однако я хочу сделать больше. Я хочу убедиться, что B сам SomeoneElse. Как я могу это сделать?


person Hot.PxL    schedule 14.05.2015    source источник
comment
static_cast ничего не навязывает; он просто вызывает неопределенное поведение, если *this на самом деле не является T.   -  person T.C.    schedule 14.05.2015
comment
@ T.C. Выполняет статические проверки. Тот, о котором вы говорите, должен быть reinterpret_cast.   -  person Hot.PxL    schedule 14.05.2015
comment
Он проверяет только то, что T является производным от Base<T>, а не *this является T. т.е., учитывая class Bar : public Base<Bar> {}; class Foo : public Base<Bar> {}; , ваш чек ничего не поймает.   -  person T.C.    schedule 14.05.2015


Ответы (3)


Сделайте конструктор (или деструктор) Base закрытым, а затем T friend. Таким образом, единственное, что может создать / разрушить Base<T>, - это T.

person T.C.    schedule 14.05.2015
comment
См. stackoverflow.com/questions/702650 /. Кажется, вы не можете подружиться с параметром шаблона. - person Hot.PxL; 14.05.2015
comment
@ Hot.PxL Ответ, получивший наибольшее количество голосов в этом вопросе, объясняет, как это сделать в конце (friend T;, а не friend class T;). - person T.C.; 14.05.2015
comment
Ты прав. Но есть ли способ сделать это там, где C ++ 11 недоступен? - person Hot.PxL; 14.05.2015

Если ваш класс содержит код, который говорит:

T* pT = 0;
Base *pB = pT;

Тогда будет ошибка компилятора, если T не совместим по присваиванию с Base.

Этот вид проверки формализован в C ++ 11, поэтому вам не нужно писать его вручную, и вы можете получить полезные сообщения об ошибках:

#include <type_traits>

template<typename T>
class Base {

public:
    void do_something() 
    {
        static_assert(
            std::is_base_of<Base, T>::value,
            "T must be derived from Base");
    }
};

class B : public Base<B> { };

int main()
{
    B b;
    b.do_something();
}

Что касается обеспечения того, чтобы параметр типа Base был именно тем классом, который является производным от него, это кажется концептуально ошибочным. Класс, который действует как базовый класс, не может «говорить о» типе, который его наследует. Он может быть унаследован более одного раза посредством множественного наследования или не унаследован вовсе.

person Daniel Earwicker    schedule 14.05.2015
comment
Я думаю, это удовлетворяет только одну сторону уравнения. Я также хочу убедиться, что B равно SomeoneElse. - person Hot.PxL; 14.05.2015
comment
Это не удается во всех случаях, на clang3.6 он жалуется, что производный класс не завершен во время static_assert. - person Richard Hodges; 14.05.2015
comment
@RichardHodges Может зависеть от того, куда вы положили static_assert. Я добавил полный пример, который кажется подходящим для MSVC 2013. Попробую его на последнем clang, когда у меня будет возможность. - person Daniel Earwicker; 14.05.2015
comment
@DanielEarwicker да, это работает, когда вы помещаете static_assert в функцию. - person Richard Hodges; 14.05.2015

Пока что два хороших ответа. Вот еще один, который использует идиому создания настраиваемых ключей доступа к определенным методам (в данном случае конструктор). Он обеспечивает абсолютную гарантию правильного использования, не подвергая закрытые методы в базе производным.

Его также можно использовать для управления доступом к другим методам базового класса на индивидуальной основе.

template<class Derived>
struct Base
{
private:
    // make constructor private
    Base() = default;
protected:
    // This key is protected - so visible only to derived classes
    class creation_key{
        // declare as friend to the derived class
        friend Derived;
        // make constructor private - only the Derived may create a key
        creation_key() = default;
    };

    // allow derived class to construct me with a key
    Base(creation_key)
    {}

    // other methods available to the derived class go here

private:
    // the rest of this class is private, even to the derived class
    // (good encapsulation)
};

struct D1 : Base<D1>
{
    // provide the key
    D1()
    : Base<D1>(creation_key())
    {}

};
person Richard Hodges    schedule 14.05.2015