2

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

};
  • What's the `S` template parameter for? – TartanLlama Apr 24 '15 at 09:26
  • How are the needed `N` defined ? – Jarod42 Apr 24 '15 at 10:05
  • @Jarod42 If I understood your question correctly, the needed `N` are defined in the `getOutputImpl` by the user (i.e. the developer of the class). However, there is a better solution - see the accepted answer. It may also better explain what I was trying to do. –  Apr 24 '15 at 21:57
  • I meant, how do we know that `PipeOutputClass` should implement `0` and `1` (and not `2`) ? – Jarod42 Apr 25 '15 at 12:59
  • @Jarod42 I think I understand your question better now. `PipeOutputClass` can have as many outport ports as the user likes, provided that they are defined consequentially. These output functions are managed in the base class (i.e. `AlgorithmOutput`) by storing pointers to them in a vector container. You can find more details here: [link](http://stackoverflow.com/questions/29733250/fill-a-vector-with-pointers-to-partially-specialized-function-members-automatica) –  Apr 25 '15 at 13:18

1 Answers1

3

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;
    }
};
TartanLlama
  • 63,752
  • 13
  • 157
  • 193