Question

In C++, is it possible to map an overloaded function over a heterogenous tuple? For example:

double f(dobule);
size_t f(std::string);

auto t = std::make_tuple(3.14, "a string");
// should be the same as std::make_tuple(f(std::get<0>(t)), f(std::get<1>(t)));
map(f, make_tuple(3.14, "a string")); // type std::tuple<double, size_t>

I can write a map function which maps the same overloaded instance of f to each tuple element (code below), but I don't see how to defer the overloading resolution of f from the call to map to the invocation of f inside map. Has anyone figured out how to do this?

Here's my code to map a single instance of an overloaded function over a tuple (where seq and gens are taken from this answer https://stackoverflow.com/a/7858971/431282):

// basically C++14's integer sequence
template<int ...>
struct seq { };

template<int N, int ...S>
struct gens : gens<N-1, N-1, S...> { };

template<int ...S>
struct gens<0, S...> {
  typedef seq<S...> type;
};

template<typename R, typename A, typename ...B, int ...S> auto
map_1(R (*f)(A), std::tuple<B...> &&t, seq<S...>)
  -> decltype(std::make_tuple(f(std::get<S>(t))...))
{
  return std::make_tuple(f(std::get<S>(t))...);
}

template<typename R, typename A, typename ...B> auto
map(R (*f)(A), std::tuple<B...> &&t)
  -> decltype(map_1(f, std::forward<std::tuple<B...>>(t), typename gens<sizeof...(B)>::type()))
{
  return map_1(f, std::forward<std::tuple<B...>>(t), typename gens<sizeof...(B)>::type());
}
Was it helpful?

Solution

The issue is that it can't be done with function pointers alone, since you want to resolve the function overload when it's bound to the argument. You can't get a function pointer to function without performing overload resolution. The key is to provide a function object that performs the overload on call, as opposed to trying to get a function pointer at the start.

For this, I would declare the primary function as

template<typename Func, typename ...B> auto
map(Func&& f, std::tuple<B...> &&t)
  -> decltype(map_1(std::forward<Func>(f), std::forward<std::tuple<B...>>(t), typename gens<sizeof...(B)>::type()))
{
  return map_1(std::forward<Func>(f), std::forward<std::tuple<B...>>(t), typename gens<sizeof...(B)>::type());
}

And then defined map_1 in a similar fashion.

Then you can make a function object wrapper

struct Wrapper
{
   template<typename T>
   auto operator()(T&& t) const -> decltype( f(std::forward<T>(t)) )
   {
       return f(std::forward<T>(t));
   }
};

and call it with map(Wrapper(), make_tuple(3.14, "a string"))

Edit: if you have C++14, you can do the following (thanks @MooingDuck for inspiration)

#define BINDOVERLOADED(X) [](auto&& t) { return X(std::forward<decltype(t)>(t)); }
auto x = map(BINDOVERLOADED(f), make_tuple(3.14, "a string"));

See http://coliru.stacked-crooked.com/a/439f958827de8cf2

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top