zoo
zoo

Reputation: 3

Template deduction of lambda arguments

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.

Upvotes: 0

Views: 88

Answers (1)

walnut
walnut

Reputation: 22162

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.

Upvotes: 1

Related Questions