Частичная специализация шаблона для указателя шаблона на функцию

Рассмотрим следующий реферат Subscription учебный класс:

template <typename TMessage>
class Subscription {
public:
    virtual ~Subscription() {}
    virtual bool handle(const TMessage &) = 0;
};

В некоторых случаях было бы удобно, если бы один класс мог реализовать этот абстрактный класс несколько раз - даже для одного и того же TMessage - а не насильственное наследование.

Чтобы достичь этого, я использую указатель шаблона для работы следующим образом:

template <typename TMessage, typename TCaller, bool(TCaller::*TMethod)(const TMessage &)>
class Invoker : public Subscription<TMessage> {
public:
    Invoker(TCaller *caller) :
        m_caller(*caller) {
    }

    virtual bool handle(const TMessage &message) {
        return (m_caller.*TMethod)(message);
    }

protected:
    TCaller &m_caller;
};

Это позволяет создавать сгенерированные шаблоном реализации абстрактного класса следующим образом:
(вместо реализации в нескольких разных классах и разделения объектов между ними):

struct MyMessage {};

class Logic {
public:
    Logic() :
        m_subscription1(new Invoker<MyMessage, Logic, &Logic::handleSubscription1>(this)),
        m_subscription2(new Invoker<MyMessage, Logic, &Logic::handleSubscription2>(this)) {
    }

    ~Logic() {
        delete m_subscription1;
        delete m_subscription2;
    }

    bool handleSubscription1(const MyMessage &message) {
        // handle message... uses class members
        return true;
    }

    bool handleSubscription2(const MyMessage &message) {
        // handle message... uses class members
        return false;
    }

private:
    Subscription<MyMessage> *m_subscription1;
    Subscription<MyMessage> *m_subscription2;
};

Дело в том, что я хотел бы разрешить пользовательские модули (т.е. Logic класс) реализация void функция ручки, а также bool вариант.

Теперь, конечно, я мог бы создать 2 разных Invoker классы - один реализован так же, как в приведенном выше коде, а другой принимает третий аргумент шаблона функции, возвращающей пустоту: void(TCaller::*TMethod)(const TMessage &), В функции handle функции возврата void я бы вызвал указанную функцию и вернул true.

Но мне было интересно, есть ли способ использовать тот же Invoker имя класса, так что пользователю не нужно будет соответствовать право Invoker к возвращаемому значению его функции дескриптора. Вроде как перегрузка функций - но для шаблонных классов.

Конечно, если бы я просто создал два класса с одним и тем же именем (каждый из которых принимает разные аргументы шаблона функции возвращаемого типа), компилятор потребовал бы переопределения параметра шаблона.
Поэтому я попытался добиться этого с частичной специализацией шаблона, но не смог найти способ определить третий аргумент шаблона, не получив ошибку компиляции - я даже не уверен, что это правильный путь.

Когда я попробовал это:

template <typename TMessage, typename TCaller, typename TMethod>
class Invoker : public Subscription<TMessage> {
};

template <typename TMessage, typename TCaller, bool(TCaller::*TMethod)(const TMessage &)>
class Invoker<TMessage, TCaller, TMethod> {
public:
    Invoker(TCaller *caller) :
        m_caller(*caller) {
    }

    virtual bool handle(const TMessage &message) {
        return (m_caller.*TMethod)(message);
    }

protected:
    TCaller &m_caller;
};

template <typename TMessage, typename TCaller, void(TCaller::*TMethod)(const TMessage &)>
class Invoker<TMessage, TCaller, TMethod> {
public:
    Invoker(TCaller *caller) :
        m_caller(*caller) {
    }

    virtual bool handle(const TMessage &message) {
        (m_caller.*TMethod)(message);
        return true;
    }

protected:
    TCaller &m_caller;
};

Я получил следующие ошибки компиляции в обеих реализациях Invoker:

ошибка: несоответствие типа / значения в аргументе 3 в списке параметров шаблона для класса шаблона Invoker
ошибка: ожидал тип, получил TMethod

И следующие ошибки компиляции в классе логики (для каждого Invoker):

В конструкторе Logic::Logic():
ошибка: несоответствие типа / значения в аргументе 3 в списке параметров шаблона для класса шаблона Invoker
ошибка: ожидал тип, получил &Logic::handleSubscription1
ошибка: неверное преобразование из логики * const в int
ошибка: невозможно преобразовать int* в подписку * при инициализации

Что я делаю не так и возможно ли это с частичной специализацией шаблона? Если есть какое-либо возможное решение, я хотел бы знать, возможно ли это в C++11, а также в C++98.

Благодарю.

1 ответ

Решение

Вы можете сделать

template <typename TMethod, TMethod method> class Invoker;

// partial specialization for TMethod = bool(TCaller::*)(const TMessage &)
template <typename TMessage, typename TCaller, bool(TCaller::*method)(const TMessage &)>
class Invoker<bool(TCaller::*)(const TMessage &), method> :
    public Subscription<TMessage>
{
public:
    explicit Invoker(TCaller& caller) : m_caller(caller) {}

    virtual bool handle(const TMessage &message) {
        return (m_caller.*method)(message);
    }
protected:
    TCaller &m_caller;
};

// partial specialization for TMethod = void(TCaller::*)(const TMessage &)
template <typename TMessage, typename TCaller, void(TCaller::*method)(const TMessage &)>
class Invoker<void(TCaller::*)(const TMessage &), method> :
    public Subscription<TMessage>
{
public:
    explicit Invoker(TCaller& caller) : m_caller(*caller) {}

    virtual bool handle(const TMessage &message) {
        (m_caller.*method)(message);
        return true;
    }
protected:
    TCaller &m_caller;
};

И использование это что-то вроде:

class Logic {
public:
    Logic() :
        m_subscription1(std::make_unique<Invoker<decltype(&Logic::handleSubscription1), &Logic::handleSubscription1>>(*this)),
        m_subscription2(std::make_unique<Invoker<decltype(&Logic::handleSubscription2), &Logic::handleSubscription2>>(*this)) {
    }

    bool handleSubscription1(const MyMessage &message) {
        // handle message... uses class members
        return true;
    }

    void handleSubscription2(const MyMessage &message) {}

private:
    std::unique_ptr<Subscription<MyMessage>> m_subscription1;
    std::unique_ptr<Subscription<MyMessage>> m_subscription2;
};
Другие вопросы по тегам