CRTP применяется к шаблону класса - PullRequest
0 голосов
/ 15 ноября 2018

Давайте рассмотрим класс шаблона CRTP Print, который предназначен для печати производного класса:

template <typename T>
struct Print {
    auto print() const -> void;
    auto self() const -> T const & {
        return static_cast<T const &>(*this);
    }

private:
    Print() {}
    ~Print() {}

    friend T;
};

Поскольку я хочу специализировать печать на основе производного класса, как мы могли бы сделать это с переопределением, я полагаю,пока не реализуем метод.

Мы можем обернуть целое число и сделать это, например:

class Integer :
    public Print<Integer>
{
public:
    Integer(int i) : m_i(i) {}

private:
    int m_i;

    friend Print<Integer>;
};

template <>
auto Print<Integer>::print() const -> void {
    std::cout << self().m_i << std::endl;
}

Пока это работает, скажем, я хочу напечатать универсальную версиюwrapper:

template <typename T>
class Wrapper :
  public Print<Wrapper<T>>
{
public:
    Wrapper(T value) : m_value(std::move(value)) {}

private:
    T m_value;

    friend Print<Wrapper<T>>;
};

Если я специализируюсь на методе печати со специализацией Wrapper, он компилируется и работает:

template <>
auto Print<Wrapper<int>>::print() const -> void
{
  cout << self().m_value << endl;
}

Но если я хочу сказать «для всех специализаций Wrapper,do this ", это не сработает:

template <typename T>
auto Print<Wrapper<T>>::print() const -> void
{
  cout << self().m_value << endl;
}

Если я выполню эту функцию через следующую основную функцию:

auto main(int, char**) -> int {
    auto i = Integer{5};
    i.print();

    auto wrapper = Wrapper<int>{5};
    wrapper.print();

    return 0;
}

Печать компилятора:

50:42: error: invalid use of incomplete type 'struct Print<Wrapper<T> >'
6:8: error: declaration of 'struct Print<Wrapper<T> >'

Зачем ?Как я могу это сделать ?Это вообще возможно, или я должен сделать полную специализацию моего класса CRTP?

1 Ответ

0 голосов
/ 15 ноября 2018

Вы можете сделать это немного окольным путем, если будете осторожны.

Live Demo

Ваш класс Print будет опираться наеще один класс PrintImpl для печати. ​​

#include <type_traits>

template<class...>
struct always_false : std::false_type{};

template<class T>
struct PrintImpl
{
    void operator()(const T&) const
    {
        static_assert(always_false<T>::value, "PrintImpl hasn't been specialized for T");
    }
};

Вы будете частично специализировать этот PrintImpl для своего Wrapper класса:

template<class T>
struct PrintImpl<Wrapper<T>>
{
    void operator()(const Wrapper<T>& _val) const
    {
       std::cout << _val.m_value;
    }
};

И убедитесь, что Wrapper объявляет это PrintImpl как friend:

friend struct PrintImpl<Wrapper<T>>;

Класс Print создает экземпляр PrintImpl и вызывает operator():

void print() const
{
    PrintImpl<T>{}(self());
}

Это работает до тех пор, пока ваши специализации объявлены до того, как вы на самом деле создадите экземпляр класса Print.


Вы также можете полностью специализировать PrintImpl<T>::operator() для своего класса Integer без написания специализации класса:

class Integer :
    public Print<Integer>
{
public:
    Integer(int i) : m_i(i) {}

private:
    int m_i;

    friend PrintImpl<Integer>;
};

template <>
void PrintImpl<Integer>::operator()(const Integer&  wrapper) const {
    std::cout << wrapper.m_i << std::endl;
}
...