J. Doe
J. Doe

Reputation: 43

CRTP: enable methods in base class based on derived class contents

Is there a way to query derived class' contents from a CRTP base class, to use with SFINAE to enable or disable base class methods?

What I'm trying to accomplish might look like the following:

template<typename Derived>
struct base
{
    struct foo {};
    struct bar {};

    void dispatch(int i)
    {
        switch (i) {
        case 0: dispatch(foo{}); break;
        case 1: dispatch(bar{}); break;
        default: break;
        }
    }

    // catch all for disabled methods
    template<typename T> void dispatch(T const&) {}

    std::enable_if</* magic that checks if there is in fact Derived::foo(foo) */>
      dispatch(foo f)
    {
        static_cast<Derived*>(this)->foo(f);
    }
    std::enable_if</* magic that checks if there is in fact Derived::bar(bar) */>
      dispatch(bar b)
    {
        static_cast<Derived*>(this)->bar(b);
    }
};

struct derived: public base<derived>
{
    // only foo in this one
    void foo(foo) { std::cout << "foo()\n"; }
};

Simply trying to use Derived::foo inside enable_if results in an error citing invalid use of an incomplete class (derived).

Upvotes: 3

Views: 589

Answers (1)

skypjack
skypjack

Reputation: 50550

Is there a way to query derived class' contents from a CRTP base class, to use with SFINAE to enable or disable base class methods?

Yes, it is. It follows a minimal, working example:

#include<iostream>

template<typename D>
class base {
    template<typename T = D>
    auto dispatch(int) -> decltype(std::declval<T>().foo(), void()) {
        static_cast<T*>(this)->foo();
    }

    void dispatch(char) {
        std::cout << "base" << std::endl;
    }

public:
    void dispatch() {
        dispatch(0);
    }
};

struct derived1: base<derived1> {
    void foo() {
        std::cout << "derived1" << std::endl;
    }
};

struct derived2: base<derived2> {};

int main() {
    derived1 d1;
    derived2 d2;
    d1.dispatch();
    d2.dispatch();
}

Adding parameters to be forwarded is straightforward and I prefer to keep the example as simple as possible.
See it running on wandbox.

As you can see from the snippet above, the basic idea is to use tag dispatching and overloaded methods to enable or disable the method in the base class and use the one in the derived class if it exists.

Simply trying to use Derived::foo inside enable_if results in an error citing invalid use of an incomplete class (derived).

That's because Derived is actually incomplete when you try to use it. The standard says:

A class is considered a completely-defined object type (or complete type) at the closing } of the class-specifier.

In your case, the derived class has a base class template and the former isn't a complete type during the instantiation of the latter for obvious reasons.
Moreover, Derived isn't an actual type within your sfinae expressions and (let me say) sfinae doesn't work in this case. That's why I did the following in the example:

template<typename T = D>
auto dispatch(int) -> decltype(std::declval<T>().foo(), void()) {
    static_cast<T*>(this)->foo();
}

Of course, decltype used that way is a sfinae expression as well. You can do something similar with std::enable_if_t if you prefer. I find this version easier to read and understand.


That being said, you can get the same result by means of a virtual method. Just use it if you don't have a good reason not to do that.


For the sake of completeness, your example updated to the technique above mentioned:

#include<iostream>

template<typename Derived>
struct base
{
    struct foo {};
    struct bar {};

    void dispatch(int i)
    {
        switch (i) {
        case 0: dispatch(0, foo{}); break;
        case 1: dispatch(0, bar{}); break;
        default: break;
        }
    }

    template<typename T>
    void dispatch(char, T const&) {}

    template<typename D = Derived>
    auto dispatch(int, foo f)
    -> decltype(std::declval<D>().foo(f), void())
    {
        static_cast<D*>(this)->foo(f);
    }

    template<typename D = Derived>
    auto dispatch(int, bar b)
    -> decltype(std::declval<D>().bar(b), void())
    {
        static_cast<D*>(this)->bar(b);
    }
};

struct derived: public base<derived>
{
    void foo(foo) { std::cout << "foo" << std::endl; }
};

int main() {
    derived d;
    d.dispatch(0);
    d.dispatch(1);
}

See it on wandbox.

Upvotes: 1

Related Questions