0

I currently have a number of data accessors like:

template <typename Fn>
typename std::result_of<Fn(DataA)>::type GetDataA(Fn fn) {
   DataA a = DoSomeWorkToGetA();
   return fn(a);
}

template <typename Fn>
typename std::result_of<Fn(DataB)>::type GetDataB(Fn fn) {
   DataB b = DoSomeWorkToGetB();
   return fn(b);
}

Which are used like:

auto computation_result_a = GetDataA([](DataA a) {
   return DoCustomProcessingOnA(a);
});

auto computation_result_b = GetDataB([](DataB a) {
   return DoCustomProcessingOnB(b);
});

What I'd like, is for a way to automatically generate combinations of these Getters, like:

template <typename Fn>
typename std::result_of<Fn(DataA, DataB)>::type GetDataAandB(Fn fn) {
   DataA a = GetDataA([](DataA a) { return a; });
   DataB b = GetDataB([](DataB b) { return b; });
   return fn(a, b);
}

I have seen this type of thing done in the past, with a user API like:

auto computation_result_a = GetData<A>([](Data a) { /**/ });
auto computation_result_b = GetData<A>([](Data b) { /**/ });
auto computation_result_ab = GetData<A,B>([](Data a, Data b) { /**/ });

But am unsure how to accomplish this.

zoo
  • 3
  • 2

1 Answers1

1

First, you will need a map from type (or tag) to correct DoSomeWorkToGet* function, e.g.:

template<typename T> /* or an enum instead of typename */
void DoSomeWorkToGet() = delete;

template<>
auto DoSomeWorkToGet<A>() { return DoSomeWorkToGetA(); }

template<>
auto DoSomeWorkToGet<B>() { return DoSomeWorkToGetB(); }

Then writing the function is just a matter of using a variadic template and a pack expansion:

template<typename... Ts, typename F> /* or an enum instead of the first typename */
auto GetData(F f) {
    return f(DoSomeWorkToGet<Ts>()...);
}

To be used as e.g.:

auto computation_result_ab = GetData<A, B>([](auto a, auto b) {
    return /* something with a and b */;
});

If you require the DoSomeWorkToGet functions to be evaluated in-order left-to-right, you cannot use the function above, but you can use the following:

template<typename... Ts, typename F> /* or an enum instead of the first typename */
auto GetData(F f) {
    using tuple_type = std::tuple<decltype(DoSomeWorkToGet<Ts>())&&...>;
    return std::apply(f, tuple_type{DoSomeWorkToGet<Ts>()...});
}

This requires C++17. Implementation with C++11/C++14 is also possible, but a will need slightly more work (to implement std::apply or a weaker form of it). It guarantees the evaluation order, because list-initialization (used in tuple_type{...}) is always strictly sequenced left-to-right.

walnut
  • 21,629
  • 4
  • 23
  • 59
  • Thank you for the response. This is fantastic. Is there any specified behavior for the order of DoSomeWorkToGet() and DoSomeWorkToGetBack() when calling Get()? Or is this unspecified? – zoo Mar 25 '20 at 09:14
  • @user13120497 *Indeterminately sequenced* (so any order might happen, but no interleaving). If you require them to be executed in order, then you will need to make some small adjustments. Is that the case? – walnut Mar 25 '20 at 09:26
  • @user13120497 Sorry, did you actually mean the evaluation order? Or are you just talking about the order of which call's result is assigned to which function argument? Because the latter is well-defined, just left-to-right through the parameters. – walnut Mar 25 '20 at 09:27
  • Sorry - the evaluation order. I.e. Similar to the unspecified order-of-evaluation of `a` and `b` here: `foo(a(), b());` – zoo Mar 25 '20 at 18:10
  • @user13120497 See my first comment and the edit to my answer, then. – walnut Mar 25 '20 at 18:30
  • Precisely what I need. I actually arrived at the same solution, and wanted to verify with you. Thank you for your time and help - I've learnt a lot. – zoo Mar 25 '20 at 18:42