简体   繁体   中英

CRTP applied on a template class

Let's consider a CRTP template class Print which is meant to print the derived class:

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

Because I want to specialize print based on the derived class like we could do this with an override, I don't implement the method yet.

We can wrap an Integer and do so for example:

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

This works so far, now let's say I want to Print a generic version of a 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>>;
};

If I specialize my print method with a specialization of the Wrapper it compile and works:

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

But if I want to say "for all specializations of Wrapper, do that", it doesn't work:

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

If I run this over the following main function:

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

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

    return 0;
}

The compiler print:

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

Why ? How can I do that ? Is it even possible or do I have to make a complete specialization of my CRTP class ?

You can do this in a bit of a roundabout way so long as you're careful.

Live Demo

Your Print class will rely on yet another class PrintImpl to do the printing.

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

You'll partially specialize this PrintImpl for your Wrapper class:

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

And make sure that Wrapper declares this PrintImpl to be a friend :

friend struct PrintImpl<Wrapper<T>>;

The Print class creates an instance of PrintImpl and calls operator() :

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

This works so long as your specializations are declared before you actually instantiate an instance of the Print class.


You can also fully specialize PrintImpl<T>::operator() for your Integer class without writing a class specialization:

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM