1

I have a template class that implements a kind of dispatcher: the class processes "messages" which have "keys", and a part of the key is used to decide what "handler" to pass the message to. The user has to derive from the template class and provide the mapping from "keys" to the part of the key used for dispatch. I thought that a pure virtual function is a natural way to model that:

template<typename Key>
class Message {
public:
    using key_type = Key;

    /* more stuff which is irrelevant */
};

template<typename Message, typename KeyPortion, typename Handler>
class Dispatcher {
public:
    virtual KeyPortion toHandlerKey(typename Message::key_type) = 0;

    /* a bit more stuff which is irrelevant */
};

The user is forced to derive, and is forced to override the function. This is good.

Now, there are a couple situations where I could provide a default implementation for toHandlerKey:

  1. in the simplest case, KeyPortion is the same as typename Message::key_type, so I could provide:

    virtual KeyPortion toHandlerKey(typename Message::key_type key) {
        return key;
    }
    
  2. most of the time, Message::key_type is a std::tuple containing KeyPortion, so I could provide:

    virtual KeyPortion toHandlerKey(typename Message::key_type key) {
        return std::get<KeyPortion>(key);
    }
    

But I have no idea how to write that properly. I can't use enable_if, because I'm not trying to overload anything. I can't provide a blanket implementation either, because then the user might forget to override it.

In summary, I want my template class to provide one of the three:

  1. a trivial virtual implementation for case (1) above;
  2. a trivial virtual implementation for case (2) above;
  3. a pure virtual function which will force the user to define their own implementation.

Is there a way to achieve that?

1 Answers1

2

You can make the class SFINAE friendly, and then provide specialization:

template<typename Message, typename KeyPortion, typename Handler, typename Enabler = void>
class Dispatcher {
public:
    virtual KeyPortion toHandlerKey(typename Message::key_type) = 0;

    /* a bit more stuff which is irrelevant */
};

template<typename Message, typename KeyPortion, typename Handler>
class Dispatcher<Message,
                 KeyPortion,
                 Handler,
                 std::enable_if_t<std::is_same<KeyPortion, typename Message::key_type>>>
{
public:
    virtual KeyPortion toHandlerKey(typename Message::key_type key)
    {
        return key;
    }

    /* a bit more stuff which is irrelevant */
};

template<typename Message, typename KeyPortion, typename Handler>
class Dispatcher<Message,
                 KeyPortion,
                 Handler,
                 std::void_t<decltype(std::get<KeyPortion>(std::declval<typename Message::key_type>()))>>
{
public:
    virtual KeyPortion toHandlerKey(typename Message::key_type key)
    {
        return std::get<KeyPortion>(key);
    }

    /* a bit more stuff which is irrelevant */
};

Common part could be factorized with Base class for example.

Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • Thank you! Turns out this technique even has a name: class template SFINAE https://cpppatterns.com/patterns/class-template-sfinae.html I ended up flipping this around, and moving `toHandlerKey` into a class of its own, then inheriting `Dispatcher` from it. Also, I couldn't grok the decltype/declval magic, so I used this more obvious trick to check if tuple contains a given type: https://stackoverflow.com/questions/25958259/how-do-i-find-out-if-a-tuple-contains-a-type/41171291#41171291 – Alexander Batischev Aug 28 '19 at 11:57
  • decltype/declval magic is to test if I can call `std::get(key)` (so not necessary only `tuple`). `decltype` is to have a non evaluated context, `std::declval()` is to have a `T` instance. Alternative `T{}` would require default constructible type. – Jarod42 Aug 28 '19 at 12:16