Search code examples
c++templatesc++11pipelinepartial-specialization

C++ Partial template specialization - design simplification


I am working on a pipeline/dataflow design pattern. I have a class 'algorithm data output' (AlgorithmOutput) that acts as an interface between two connected network segments. In particular, it provides method templates getOutput<size_t N> that are used for the data output from an object of the type 'data transmitter'.

The current design is based on the idea that users derive from the class AlgorithmOutput and provide a finite number of implementations of the method template getOutput<size_t N>. I also need to be able to allow users to provide their own custom return types from the method getOutput (i.e. the return type cannot be polymorphic). Moreover, it is necessary to have all implementations of getOutput to be able to access the same data set defined as a member of the class to which the methods belong.

The current solution uses partial explicit specialisation in the classes derived by the user to define the different implementations of the method getOutput. I would like to simplify the solution and would appreciate any ideas on how this can be done without losing the functionality of the current design.

EDIT: I am only concerned about the ease of implementation of the method getOutput from the perspective of the user. I am not concerned about how complex is the implementation of the base classes.

An example of the implementation of the derived class:

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;
            }
    };

};

Solution

  • You could use tag dispatching to avoid the need for partial specialization. A simplified version:

    //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;
        }
    };