C++ Частичная специализация шаблонов — упрощение дизайна

Я работаю над шаблоном проектирования конвейера/потока данных. У меня есть класс "вывод данных алгоритма" (AlgorithmOutput), который действует как интерфейс между двумя подключенными сегментами сети. В частности, он предоставляет шаблоны методов getOutput<size_t N>, которые используются для вывода данных из объекта типа «передатчик данных».

Текущий дизайн основан на идее, что пользователи происходят от класса AlgorithmOutput и предоставляют конечное число реализаций шаблона метода getOutput<size_t N>. Мне также нужно разрешить пользователям предоставлять свои собственные настраиваемые типы возвращаемых данных из метода getOutput (т. е. возвращаемый тип не может быть полиморфным). Более того, необходимо, чтобы все реализации getOutput имели возможность доступа к одному и тому же набору данных, определенному как член класса, к которому принадлежат методы.

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

РЕДАКТИРОВАТЬ: Меня беспокоит только простота реализации метода getOutput с точки зрения пользователя. Меня не беспокоит сложность реализации базовых классов.

Пример реализации производного класса:

class PipeOutputClass: public AlgorithmOutput<PipeOutputClass>
{

public:

    template <size_t N>
    auto getOutput(size_t c_Idx) const
        {
            return getOutputImpl<N>::apply(this, c_Idx);
        }

    template<size_t N, typename S> friend struct getOutputImpl;

    template<size_t N, typename = void>
    struct getOutputImpl
    {
        static auto apply(
            PipeOutputClass const* p_Self,
            size_t c_Idx
            )
            {
                throw std::runtime_error("Wrong template argument.");
            }
    };

    template <typename S>
    struct getOutputImpl<0, S>
    {
        static std::unique_ptr<double> apply(
            PipeOutputClass const* p_Self,
            size_t c_Idx
            )
            {
                std::unique_ptr<double> mydouble(new double(10));
                return mydouble;
            }
    };

    template <typename S>
    struct getOutputImpl<1, S>
    {
        static std::unique_ptr<int> apply(
            PipeOutputClass const* p_Self,
            size_t c_Idx
            )
            {
                std::unique_ptr<int> myint(new int(3));
                return myint;
            }
    };

};

person Community    schedule 24.04.2015    source источник
comment
Для чего нужен параметр шаблона S?   -  person TartanLlama    schedule 24.04.2015
comment
Как определяются необходимые N?   -  person Jarod42    schedule 24.04.2015
comment
@Jarod42 Jarod42 Если я правильно понял ваш вопрос, необходимые N определяются в getOutputImpl пользователем (то есть разработчиком класса). Однако есть лучшее решение - см. принятый ответ. Это также может лучше объяснить, что я пытался сделать.   -  person    schedule 25.04.2015
comment
Я имел в виду, откуда мы знаем, что PipeOutputClass должен реализовать 0 и 1 (а не 2)?   -  person Jarod42    schedule 25.04.2015
comment
@ Jarod42 Я думаю, теперь я лучше понимаю твой вопрос. PipeOutputClass может иметь столько выходных портов, сколько пожелает пользователь, при условии, что они определены последовательно. Эти выходные функции управляются в базовом классе (т. е. AlgorithmOutput) путем хранения указателей на них в векторном контейнере. Более подробную информацию можно найти здесь: ссылка   -  person    schedule 25.04.2015


Ответы (1)


Вы можете использовать диспетчеризацию тегов, чтобы избежать частичной специализации. Упрощенная версия:

//we'll use this to overload functions based on a size_t template param
template <size_t N>
struct Size2Type{};

class PipeOutputClass
{
public:
    template <size_t N>
    auto getOutput(size_t c_Idx) const
    {
        //use Size2Type to tag dispatch
        return getOutputImpl(Size2Type<N>{}, c_Idx);
    }

    //default version for when we don't explicitly provide an overload
    template <size_t N>
    auto getOutputImpl(Size2Type<N>, size_t c_Idx) const
    {
         throw std::runtime_error("Wrong template argument.");
    }

    //overload for when N = 0
    std::unique_ptr<double> getOutputImpl (Size2Type<0>, size_t c_Idx) const
    {
        std::unique_ptr<double> mydouble(new double(10));
        return mydouble;
    }

    //overload for when N = 1
    std::unique_ptr<int> getOutputImpl (Size2Type<1>, size_t c_Idx) const
    {
        std::unique_ptr<int> myint(new int(3));
        return myint;
    }
};
person TartanLlama    schedule 24.04.2015