Can I get the return type of multiple chained func

2019-08-02 17:53发布

问题:

I would like to store functions in an ordered collection and then apply all of them to a certain collection, which would result in obtaining heavily modified values, stored in another collection. My initial attempt consisted of creating an std::tuple of said functions and trying to get the result type (std::invoke_result) of applying all of them to a certain type:

int main() {
    auto multiply   = [](const auto arg){ return arg * arg; };
    auto change     = [](const auto arg){ return std::vector{arg}; };
    auto to_string  = [](const auto arg){ return arg.size() + " size"; };

    auto functions = std::make_tuple(multiply, change, to_string);

    std::vector<int> source{1, 2, 3, 4};

    using f_type = decltype(functions);
    using last_type =
            std::tuple_element_t<std::tuple_size_v<f_type> - 1, f_type>;
    using result_type =
            std::invoke_result_t<last_type, /* size - 2 ret type and so on */>;

    /* 
     * result_type is the type of applying *multiply* to int (type of *source*),
     * then applying *change* to the result of *multiply* and then applying
     * *to_string* to the result of *change*. Should be std::string.
     */
    std::vector<result_type> results{};
}

The problem is that the second template parameter of std::invoke_result_t needs a type that will be passed to a call operator of an object of a last_type type. That needs a deduction of the one before the last element's return type, and so on (there may be a lot of functions).

What I'm ultimately trying to achieve is to implement Java's stream library (this example would be the equivalent of chaining 3 map functions). I will also hold additional enums that will indicate whether the next element is a map, filter or any other supported function, so there will be no confusion about what should the function do - the problem for now is to get started with such logic.

Is there a way to obtain the return type of chaining an arbitraty number of functions, where the type passed to the very first one it known?

Or maybe my design is flawed so much that I should rather start again, following a completely different logic?

Disclaimer - I am well aware of the upcoming in C++20 (hopefully) rangesV3. I am trying to mimic their behaviour (with some minor changes). I am also aware of boost::adapters - their usage does not satisfy me, plus I would like to try to simply implement something similar.

回答1:

Say you have three callable objects f g h, and you want to get the type of h(g(f(args...))), you can do like this:

template <size_t first, class ChainedFns, class... Args>
decltype(auto) Call(ChainedFns &&fns, Args&&... args) {
    if constexpr (std::tuple_size_v<std::decay_t<ChainedFns>> == 0)
        return;
    else if constexpr (first < std::tuple_size_v<std::decay_t<ChainedFns>>-1)
        return Call<first + 1>(fns, std::invoke(std::get<first>(std::forward<ChainedFns>(fns)), std::forward<Args>(args)...));
    else if constexpr (first == std::tuple_size_v<std::decay_t<ChainedFns>>-1)
        return std::invoke(std::get<first>(std::forward<ChainedFns>(fns)), std::forward<Args>(args)...);
}

template <size_t first, class ChainedFns, class... Args>
struct invoke_result_of_chained_callables {
    using type = decltype(Call<first>(std::declval<ChainedFns>(), std::declval<Args>()...));
};

template <size_t first, class ChainedFns, class... Args>
using invoke_result_of_chained_callables_t = typename invoke_result_of_chained_callables<first, ChainedFns, Args...>::type;

int main() {
    auto fns = std::make_tuple(
        [](auto) { return 0; }, // f
        [](auto x) { return std::vector{ x }; }, // g
        [](auto x) { return x.size(); } // h
    );

    using type = decltype(Call<0>(fns, nullptr));
    static_assert(std::is_same_v<type, size_t>);

    using type1 = invoke_result_of_chained_callables_t<0, decltype(fns), std::nullptr_t>;
    static_assert(std::is_same_v<type, type1>);
    return 0;
}

This code snippet works for arbitrary numbers of chained callable objects too.