4

How do we compose functions that return multiple return values in C++? More specifically, if one function returns a tuple, can we compose this function with another that does not explicitly accept tuples? For example, in the code:

#include <tuple>
#include <iostream>

std::tuple <int,int> tuple_ints(int x,int y) {
    return std::tuple <int,int> (x,y);
}

int add(int x,int y) {
    return x+y;
}

int main() {
    std::cout << add(tuple_ints(1,2)) << std::endl;
}

I'm trying to compose the functions add and tuple_ints. This rightly generates the error:

g++ -std=c++11 test01.cpp -o test01
test01.cpp: In function 'int main()':
test01.cpp:17:37: error: cannot convert 'std::tuple<int, int>' to 'int' for argument '1' to 'int add(int, int)'
     std::cout << add(tuple_ints(1,2)) << std::endl;
                                     ^
Makefile:2: recipe for target 'all' failed
make: *** [all] Error 1

I don't want to modify add to accept a tuple; I want the definition to stay largely as it is. Is there something else that we can do so that we can compose these two functions?

Edit 1

It turns out that there's a proposal to add this functionality to the standard library under N3802. This is similar to the code provided by @Jarod42. I'm attaching the fixed code, which uses the code out of N3802 for reference. Mostly, the difference is that the code in the proposal appears to handle the perfect forwarding correctly

#include <tuple>
#include <iostream>
#include <utility>

// This comes from N3802
template <typename F, typename Tuple, size_t... I>
decltype(auto) apply_impl(F&& f, Tuple&& t, std::index_sequence<I...>) {
    return std::forward<F>(f)(std::get<I>(std::forward<Tuple>(t))...);
}
template <typename F, typename Tuple>
decltype(auto) apply(F&& f, Tuple&& t) {
    using Indices = 
        std::make_index_sequence<std::tuple_size<std::decay_t<Tuple>>::value>;
    return apply_impl(std::forward<F>(f), std::forward<Tuple>(t), Indices{});
}

// Now, for our example
std::tuple <int,int> tuple_ints(int x,int y) {
    return std::tuple <int,int> (x,y);
}

int add(int x,int y) {
    return x+y;
}

int main() {
    std::cout << apply(add,tuple_ints(1,2)) << std::endl;
}

Also, in case there was any confusion, this solution requires C++14 for things like std::index_sequence.

wyer33
  • 6,060
  • 4
  • 23
  • 53
  • 3
    I don't think you're composing any functions here, you're *applying* them. – Frerich Raabe Jan 06 '15 at 11:57
  • 1
    What's so terrible about adding a `tuple` overload that calls `add(int,int)`? This is by far the easiest and most readable solution, and no questions about whether the compiler will be able to optimize it out. – Damon Jan 06 '15 at 12:10
  • @Damon: In this case, yes, I could. In general, I've a lot of these functions and I don't want to overload all of them. In addition, some are provided by the user and I don't want to require them to overload the functions. But, in short, this is a common use case and I've no desire to rewrite every function of the form of `f(X,Y,Z)` into `f(std::tuple )` just so that I can chain together applications. – wyer33 Jan 06 '15 at 12:15
  • @Frerich Raabe: You are correct, but the same issue comes up in a true function composition. Mostly, I just thought the code above was easier to see what was going and it avoids talking about how to curry in C++. And, in truth, I'm more interested in the application of the composed function. – wyer33 Jan 06 '15 at 12:17

3 Answers3

9

You may add a function to dispatch tuple into argument:

namespace detail
{
    template <typename F, typename TUPLE, std::size_t...Is>
    auto call(F f, const TUPLE& t, std::index_sequence<Is...>)
    -> decltype(f(std::get<Is>(t)...))
    {
        return f(std::get<Is>(t)...);
    }
}

template <typename F, typename TUPLE>
auto call(F f, const TUPLE& t)
-> decltype (detail::call(f, t,
        std::make_index_sequence<std::tuple_size<TUPLE>::value>()))
{
    return detail::call(f, t,
        std::make_index_sequence<std::tuple_size<TUPLE>::value>());
}

And then call it that way

std::cout << call(add, tuple_ints(1,2)) << std::endl;

Live example

Jarod42
  • 203,559
  • 14
  • 181
  • 302
5

You may want to use some anonymous lambda like

  [](std::tuple <int,int> t){return add(get<0>(t),get<1>(t));}(tuple_int(1,2))

hopefully the compiler would optimize that code.

Read also about currying.

Basile Starynkevitch
  • 223,805
  • 18
  • 296
  • 547
0

You can create a function (say X) which will take std::tuple <int , int > as argument and internally it will call int add (int x, int y). Something like:

int X(std::tuple <int, int> t) {
    return add ( std::get<0>(t), std::get<1>(t) );
}

Now call X(tuple_ints(1,2))

Ashwani
  • 1,938
  • 11
  • 15