I'm currently toying around with some Computer Vision algorithms and always wanted to learn more about templates, so I came up with the idea to build a templated Image Processing Pipeline.

I want the pipeline to be built of several task blocks which I want to plug into each other sequentially. For a specific block, its input and output types are fixed. I ultimately want to be able to inherently control for a block to only accept the next block if the output type of the current is identical to the input type of the next.

         Block_N                 Block_N+1
     _______________         _________________ 
int |               |  str  |                 | float
--->| str = f1(int) |------>| float = f2(str) |------->
    |_______________|       |_________________|

Consequently, Block_N+2 would have to accept float as input and may have an arbitrary output type.

This is what I was able to come up with:

Abstract base classes:

template<class I>
class AbstractInput {

    virtual ~AbstractInput() { }

    virtual void call( I _input ) = 0;

template<class I, class O>
class AbstractPipelineTask : public AbstractInput<I> {

    typedef AbstractInput<O>* Ptr;

    AbstractPipelineTask( AbstractInput<O>* _nextTask = NULL) 
        : m_nextTask ( _nextTask ) { }

    virtual void call( I _input ) {

        m_input = _input;
        m_output = executeTask( m_input );

        if ( m_nextTask )
            m_nextTask->call( m_output );

    virtual O executeTask( I& _input ) = 0;

    AbstractInput<O>* m_nextTask;

    I m_input;
    O m_output;

Example implementation:

class Str2IntPipelineTask : public AbstractPipelineTask< std::string, int > {

    Str2IntPipelineTask( AbstractInput<int>* _nextTask = NULL ) 
        : AbstractPipelineTask( _nextTask ) { }
    virtual ~Str2IntPipelineTask() { }

    virtual int executeTask( std::string& _input ) {

        return atoi( _input.c_str() );

class Int2IntPipelineTask : public AbstractPipelineTask< int, int> {

    Int2IntPipelineTask( AbstractInput<int>* _nextTask = NULL ) 
        : AbstractPipelineTask( _nextTask ) { }
    virtual ~Int2IntPipelineTask() { }

    virtual int executeTask( int& _input ) {

        return ( _input * _input );

class Int2FloatPipelineTask : public AbstractPipelineTask< int, float > {

    Int2FloatPipelineTask( AbstractInput<float>* _nextTask = NULL ) 
        : AbstractPipelineTask( _nextTask ) { }
    virtual ~Int2FloatPipelineTask() { }

    virtual float executeTask( int& _input ) {

        return ( static_cast<float>( _input ) / 42.f );


int _tmain(int argc, _TCHAR* argv[])

    Int2FloatPipelineTask* pipeTask2    = new Int2FloatPipelineTask( );
    Int2IntPipelineTask* pipeTask1      = new Int2IntPipelineTask( pipeTask2 );
    Str2IntPipelineTask* pipeTask0      = new Str2IntPipelineTask( pipeTask1 );

    pipeTask0->call( "42" );

    return 0;

What I would really like to know besides some general advice is:

  • How can I get rid of the raw pointers AbstractInput<O>* m_nextTask? I tried changing the typedef of Ptr to std::shared_ptr<AbstractInput<O>> but that broke the derived classes constructors.
  • Is it possible to get rid of AbstractInput?
  • \$\begingroup\$ Do you accept C++11 or do you absolutely need to stick to C++03 for some reason? \$\endgroup\$ – Morwenn Oct 7 '14 at 18:20
  • \$\begingroup\$ Actually, I don't really care, because I'd just love to see some elegant solution. But most likely, I won't have C++11 and most parts of the stl available in my final environment \$\endgroup\$ – pdresselhaus Oct 7 '14 at 18:25
  • 1
    \$\begingroup\$ Here's one way to do this. If you get rid of all the C++11 stuff in this example, you'll have to create each pipeline task before calling operator>> in main. \$\endgroup\$ – jliv902 Oct 9 '14 at 15:35
  • Since none of these classes have private members, they can be structs instead. As such, you'll no longer need to explicitly give them public inheritance.

  • Consider making the magic number 42.f a constant so that its intent is known.

  • This is specific to Visual Studio, thus is non-portable:

    int _tmain(int argc, _TCHAR* argv[])

    It should be changed to this to fit the standard:

    int main(int argc, char* argv[])
  • In your various executeTask() functions, I believe you're meaning to pass by const&, not just by reference. You're not modifying the argument, so const is best with them.


Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.