1

lets say I want to compose functions, like processResult and sendResult, but I can't just chain them because processResult might need to call sendResult 0,1,2 or n times per each call of processResult. What is the proper way to do this in C++11?

I thought of 2 solutions:
1) give first function a std::function param (and assign processResult to it), so it can call it when it needs to.

2) (dont like this one-seems too complicated) thread safe queue, put functions in 2 threads...

@requests for example:

input 1,2,3
calls of functions:
processResult(1)
//nothing
processResult(2)
//calls:
sendResult(10)
sendResult(20)
sendResult(50)
processREsult(3)
//calls
sendREsult(55)
6
  • 5
    Could you post a small concrete example? It would help me, and possibly others, understand it. Commented Jun 5, 2013 at 11:00
  • can't you just call one function from the other? Commented Jun 5, 2013 at 11:02
  • Could you pass sendResult as a function pointer/reference, or wrapped in a function object, to processResult, and then processResult can decide internally how often and when to call it? Commented Jun 5, 2013 at 11:12
  • @hmjd idk if it helps but i updated the Q Commented Jun 5, 2013 at 16:20
  • @jogojapan that is my 1) solution :D Commented Jun 6, 2013 at 5:25

4 Answers 4

4

The composable programming, or stack based programming, model may make sense.

Each function takes a stack of arguments and returns same. These stacks can be vector or tuple or generators/generic ranges. Some means for the number of input parameters to be determined may be useful.

In this case, your generator makes a 'local' stack, then you throw that stack at the consumer until done.

If you want these operations to be interleaved, you can have the producer function return a generator function or pair of generating iterators that lazily produce your elements, or you pass it an output stack whose push method ferries it to the consumer function (ie, pass the consumer function to the producer).

The nice thing about the generator solution is that it does not fill your execution stack with crud, and you have better control over what happens next. The downside is that the producer state needs be explicitly stored, instead of living on the stack, and that the producer must be modified reasonably heavily. With lambdas this is not that bad, as you can store the next iteration of the loop as a closure, but it is still tricky.

Here is a trivial generator:

using boost::optional; // or std::optional in C++14
using boost::none_t;
template<typename T>
using Generator = std::function< optional<T>() >;

Generator<int> xrange( int min, int max, int step=1 ) {
  int next = min;
  return [=]()->optional<int> mutable
  {
    if (next > max) return {none_t};
    int retval = next;
    next += step;
    return {retval};
  };
};

If you prefer iterators, turning a Generator<T> into a generating iterator is something you only have to write once, and it works for all Generator<T>. And writing Generator<> based code is easier than writing generating iterator based code. Plus Generators and Pipes are easy to chain:

template<typename T, typename U>
using Pipe = std::function< optional<T>(U) >;

template<typename A, typename B, typename C>
auto Compose( Pipe<A, B> second, Generator<C> first )
  -> decltype( second( std::move(*first()) ) )
{
  return [=]()->optional<A> mutable {
    optional<B> b = first();
    if (!b) return {none_t};
    return second(std::move(*b));
  };
}
template<typename A, typename B, typename C, typename D>
auto Compose( Pipe<A, B> second, Pipe<C, D> first )
  -> decltype( second( std::move(*first( std::declval<D>() ) ) ) )
{
  return [=](C c)->optional<A> mutable {
    optional<B> b = first(c);
    if (!b) return {none_t};
    return second(std::move(*b));
  };
}
// until C++14, when we get auto deduction of non-lambda return types:
#define RETURNS(x) -> declval(x) { return {x}; }
template<typename A, typename B, typename C>
auto operator|( Generator<A> first, Pipe<B,C> second )
  RETURNS( Compose(second, first) )

template<typename A, typename B, typename C, typename D>
auto operator|( Pipe<A, B> first, Pipe<C,D> second ) {
  RETURNS( Compose( second, first ) )

which we then abuse like this:

struct empty {}; // easier to pass through pipes than void
template<typename T>
void sendEverything( Pipe<empty, T> sender, Generator<T> producer ) {
  Generator<empty> composed = producer | sender;
  while (composed()) {}
}

and the producer happily produces data, each of which is send on to the sender, then the producer is called again. The sender can even abort the sequence by returning a none_t.

A bit more advanced work and we'd be able to have pipes that represent one-to-many and many-to-one relationships.

(code not yet tested, so probably contains compiler errors)

template<typename Out, typename In>
using OneToManyPipe = Pipe< Generator<Out>, In >;
template<typename Out, typename In>
using ManyToOnePipe = Pipe< Out, Generator<In> >;
template<typename Out, typename In>
using ManyToManyPipe = Pipe< Generator<Out>, Generator<In> >;

template<typename Out, typename A, typename B>
auto Compose( OneToManyPipe< Out, A > second, Generator<B> first )
  -> decltype( second( std::move(*first()) ) )
{
  auto sub_gen = [=]()->optional<Generator<Out>> mutable {
    optional<B> b = first();
    if (!b) return {none_t};
    return second(std::move(*b));
  };
  optional<Generator<Out>> sub = []()->optional<Out> { return {none_t}; };
  return [sub_gen,sub]()->optional<Out> mutable {
    for(;;) {
      if (!sub)
        return {none_t};
      optional<Out> retval = (*sub)();
      if (retval)
        return retval;
      sub = sub_gen();
    }
  }
}
template<typename Out, typename A, typename B, typename C>
auto Compose( OneToManyPipe< Out, A > second, OneToManyPipe<B, C> first )
  -> OneToManyPipe< decltype( *second( std::move(*first()) ) ), C >;
// etc

probably boost already does this somewhere. :)

A downside to this approach is that eventually the overloaded operators becomes ambiguous. In particular, the difference between a OneToMany pipe and a OneToOne pipe is that the second is a subtype of the first. I suppose the optional<T> would make the OneToMany "more specialized".

This does mean that any std::function< optional<T>()> is treated like a generator, which is not right. Probably a struct generator_finished {}; variant< generator_finished, T > is a better approach than an optional, because using generator_finished in a variant of your return value when you aren't a generator seems impolite.

Sign up to request clarification or add additional context in comments.

5 Comments

IME writing generator iterators is incredibly painful. :( And end iterators stick out like a sore thumb.
Generator functions, that generate variant<finished, T>, are easier to write. And full scale variant is not usually needed -- tuple<bool, union{T data; Empty;}> is enough, or even just a tuple
I guess optional<T> would be a nice alternative as well.
@R.MartinhoFernandes How C++14 of you.
Actually, optional may br migrating in C++14. @griwes
0

If you simply need to call one function from the other as many times you want you can call one processResult inside sendResult (and vice versa, provided you forward-declare what's needed).

int processResult() { /* blah blah */ }

void sendResult() { 
    while(needed) {
        if(needed)
           processResult();
    }
}

Comments

0

May be you just put your sendResult in an class. Define an object of that class in the class of process Result. Then you can all sendResult as many times you want from process result using this object.

Comments

0

This does not actually look like function composition to me, because if it was, you would be able to simply chain them. From what I read, you require, that processResult should be able to call some external methods further processing the data. In such case, I would think about these two solutions:

  • Pass std::function to processResult. This allows you to pass more objects: a function, functor object or even a lambda into that method.
  • Create an interface, which provides all required operations and pass it to processResult.
  • Change these two methods into so-called processor classes. You would be able to use them more flexibly than if they are functions or methods, so this one should suit your needs better if complexity of your program increases.

    class VectorSender : IDataSender
    {
    private:
        std::vector<Data> & target;
    
    public:
        VectorSender(std::vector<Data> & newTarget)
            : target(newTarget)
        {
        }
    
        // Implementation of IDataSender
        void Send(Data data)
        {
            target.push_back(data);
        }
    };
    
    class Processor : IDataProcessor
    {
    private:
        sender : IDataSender;
    
    public:
        Processor(IDataSender newSender)
            : sender(newSender)
        {
        }
    
        // Implementation of IDataProcessor
        void Process()
        {
             // Do some processing
             if (sendData)
                  sender.Send(someData);
        }
    };
    

In the previous example, Sender may also get another class performing the actual sending, so you are able to "chain" more dependent objects.

Much depends on your program's architecture and I'm afraid, that no one will be able to help you further without some more detailed information about it.

2 Comments

"This does not actually look like function composition to me, because if it was, you would be able to simply chain them." And this is why "C++ supports functional programming" is bullshit. Any functional programming language would have combinators that would make it possible to chain these functions trivially :(
@R.MartinhoFernandes You know, that highly depends on how OP wants to "chain" them. If the first one randomly calls the second one multiple times and possibly second calls third one multiple times and both second and third may be parameterized, chaining them even in functional language might require some effort...

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.