1

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 ?

Steranoid
  • 64
  • 6
  • 1
    Can you post a complete example? My initial hunch is that `auto self() const -> T` is the culprit, since you actually return whole object. Even though the body casts to a reference return would copy the object. I am to lazy to compile a live example to test. – luk32 Nov 15 '18 at 15:06
  • 1
    I don't use trailing return types much, but does `self` return a copy of `this`? – François Andrieux Nov 15 '18 at 15:07
  • Yes you're right I did a copy, I didn't pay attention, but on an int this doesn't solve anything anyway. I corrected it though and add a main function. – Steranoid Nov 15 '18 at 15:15
  • Yoda notation why like you? `auto print() const -> void;` – SergeyA Nov 15 '18 at 15:24
  • I like rust ... Well, that's pretty much the only reason :P – Steranoid Nov 15 '18 at 15:25

1 Answers1

1

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;
}
Community
  • 1
  • 1
AndyG
  • 39,700
  • 8
  • 109
  • 143
  • That's a nice way, isn't creating the PrintImpl each time the function is called a little bit heavy though ? – Steranoid Nov 15 '18 at 15:20
  • @Steranoid: It's a necessary evil because you cannot partially specialize functions. So we have to use a class. – AndyG Nov 15 '18 at 15:21
  • 2
    1. It is likely optimized away. 2. You can make the specialized function a static function instead--it wouldn't have to be `operator()`. – Dark Falcon Nov 15 '18 at 15:22
  • What's the purpose of `always_false` here? – bipll Nov 15 '18 at 16:03
  • @bipll: We want the primary `PrintImpl` template to always fail the static assertion (but only if it's instantiated). This is a standards-safe way of doing it. If we said `static_assert(false, ...)` then we'd always fail the static assert whether it was instantiated or not because it didn't depend on a template argument. If we said something like `static_assert(sizeof(T) == 0, ...)` then this is technically undefined behavior. – AndyG Nov 15 '18 at 16:06
  • "then we'd always fail the static assert because it didn't depend on a template argument" when it's not a specialization, eh? – bipll Nov 15 '18 at 16:08
  • @bipll: See my edit. It will fail with or without a specialization because it will fail whether the template is instantiated or not (because the assert doesn't rely on a template parameter) [Demo](https://wandbox.org/permlink/t2dWGZN6AuZSCw8N) – AndyG Nov 15 '18 at 16:12
  • @bipll What is meant is this: The compiler parses the `PrintImpl` template, sees a `static_assert` that does not depend on template parameters, so it evaluates it immediately. You immediately get an error, no matter how/if you use or specialize `PrintImpl` anywhere. – Max Langhof Nov 15 '18 at 16:12
  • @AndyG Why is `static_assert(sizeof(T) == 0, ...)` UB? – Max Langhof Nov 15 '18 at 16:13
  • I think I will prefer a partial specialization for my template because with your solution, one will have to specialize a whole new class just to do so every time, with a specific partial specialization, one would have to specialize a whole class only if he wants to use this crtp on a template. To ensure that all Print object have the same methods, we can delegate things to a helper class that we specilize only if needed as you proposed. This gives us this : [Code](https://wandbox.org/permlink/rXnLygxwUKXAf5XO) – Steranoid Nov 15 '18 at 16:29
  • @MaxLanghof: Correction: ill-formed no diagnostic required. Since we can prove that `sizeof(T) != 0` for all `T` as required by the standard (with some exceptions for empty base subobject optimization), there's no `T` such that the primary template is valid. See [temp.res] "Knowing which names are type names allows the syntax of every template to be checked. The program is ill-formed, no diagnostic required, if: [...] no valid specialization can be generated for a template or a substatement of a constexpr if statement within a template and the template is not instantiated." – AndyG Nov 15 '18 at 16:45
  • @MaxLanghof: Also see https://stackoverflow.com/questions/27343811/static-assert-with-dependent-expression-that-is-actually-independent – AndyG Nov 15 '18 at 16:46
  • @Steranoid: I appreciate your feedback, however I don't see how your solution doesn't also require the same amount of partial specialization. – AndyG Nov 15 '18 at 16:48
  • @AndyG because it's less verbose when it comes to implement it for one function only : [Code](https://wandbox.org/permlink/jIEt8wkhuoGkaG2d) – Steranoid Nov 16 '18 at 08:27
  • 1
    @Steranoid: Fair enough, I see your point. I think that we can get the full function specialization you desire as well as the safety of the `static_assert` from my code (and no extra inheritance): https://wandbox.org/permlink/VFPy7rTdXm9I3fYh – AndyG Nov 16 '18 at 15:53
  • @AndyG Oh, I didn't even considered it like this, yeah I think this is perfect, you want to propose this final solution so I can close this post ? – Steranoid Nov 20 '18 at 10:04
  • @Steranoid: Done – AndyG Nov 20 '18 at 15:33