Searching through a tuple for arguments of a function

天涯浪子 提交于 2019-12-06 14:56:44
#include <utility>
#include <type_traits>
#include <tuple>

namespace detail {

template <std::size_t, int, typename, typename, typename=void>
constexpr std::size_t find = -1;
template <std::size_t I, int dir, typename U, typename Ts>
constexpr auto find<I, dir, U, Ts, std::enable_if_t<(I < std::tuple_size<Ts>{})>>
 = std::is_same<std::tuple_element_t<I, Ts>, U>{}? I : find<I+dir, dir, U, Ts>;

template <typename, typename ISeq, std::size_t, typename>
struct obtain_indices {using type = ISeq;};
template <typename Ts, std::size_t... Is, std::size_t u, typename Us>
struct obtain_indices<Ts, std::integer_sequence<
  std::enable_if_t<(u < std::tuple_size<Us>{}), std::size_t>, Is...>, u, Us> {
    static constexpr std::array<std::size_t, sizeof...(Is)> indices = {Is...};
    using C = std::tuple_element_t<u, Us>;
    static constexpr auto previous = find<u-1, -1, C, Us>;
    using type = typename obtain_indices<Ts, std::index_sequence<Is...,
      find<previous != -1? indices[previous]+1 : 0, 1, C, Ts>>, u+1, Us>::type;
};

// General overload once indices have been determined
template <typename Tup, typename F, std::size_t... Is>
constexpr decltype(auto) invoke(F&& f, Tup&& t,
  std::index_sequence<Is...>) {
    return std::forward<F>(f)(std::get<Is>(std::forward<Tup>(t))...);
}

} // end namespace detail

// For function pointers
template <typename Tup, typename R, typename... Args>
constexpr decltype(auto) invoke(R(*f)(Args...), Tup&& t) {
    return detail::invoke(f, std::forward<Tup>(t),
      typename detail::obtain_indices<std::decay_t<Tup>,
        std::index_sequence<>, 0, std::tuple<std::decay_t<Args>...>>::type{});
}

From your example:

#include <iostream>

double bar (int a, char c, bool b, int d) {
    std::cout << a << ' ' << c << ' ' << b << ' ' << d << '\n';
    return 2.5;
}

int main() {
    const auto tuple = std::make_tuple(5, true, 'a', 3.5,
                                       false, 1000, 't', 2, true, 5.8);
    invoke(bar, tuple);
}

Demo.

Here is an alternative I've found to Columbo's method. Instead of searching backwards through the args tuple to determine if T was searched before already, store Pair<T,index+1> in a pack. Then the search begins at position 0 only if T is not found among the Pairs in that pack, else at the index+1 position. I don't know which method is more efficient though.

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

template <typename, std::size_t> struct Pair;

template <typename Tuple, typename F, std::size_t... Is>
constexpr decltype(auto) partial_apply (Tuple&& tuple, F&& f, std::index_sequence<Is...>) {
    return std::forward<F>(f)(std::get<Is>(std::forward<Tuple>(tuple))...);
}

// FunctionTraits
template <typename> struct FunctionTraits;

template <typename R, typename... Args>
struct FunctionTraits<R(Args...)> : std::integral_constant<std::size_t, sizeof...(Args)> {
    using args_type = std::tuple<std::decay_t<Args>...>;
    using return_type = R;
};

template <typename R, typename... Args>
struct FunctionTraits<R(*)(Args...)> : FunctionTraits<R(Args...)> {};

template <typename R, typename... Args>
struct FunctionTraits<R(&)(Args...)> : FunctionTraits<R(Args...)> {};
// etc... for other callable types.

template <typename Tuple, typename T, std::size_t Start, typename = void>
struct Find : std::integral_constant<std::size_t, -1> {};

template <typename Tuple, typename T, std::size_t Start>
struct Find<Tuple, T, Start, std::enable_if_t<(Start < std::tuple_size<Tuple>::value)>> {
    static constexpr size_t value = std::is_same<T, std::tuple_element_t<Start, Tuple>>::value ? Start : Find<Tuple, T, Start+1>::value;
};

template <typename T, typename... Pairs> struct SearchPairs;

template <typename T>
struct SearchPairs<T> : std::integral_constant<std::size_t, 0> {};

template <typename T, typename First, typename... Rest>
struct SearchPairs<T, First, Rest...> : SearchPairs<T, Rest...> {};

template <typename T, std::size_t I, typename... Rest>
struct SearchPairs<T, Pair<T,I>, Rest...> : std::integral_constant<std::size_t, I> {};

template <typename Tuple, typename ArgsTuple, std::size_t Start, typename Indices, typename LastIndices, typename = void>
struct ObtainIndices {
    using type = Indices;
};

template <typename Tuple, typename ArgsTuple, std::size_t Start, std::size_t... Is, typename... Pairs>
struct ObtainIndices<Tuple, ArgsTuple, Start, std::index_sequence<Is...>, std::tuple<Pairs...>,
        std::enable_if_t<(Start < std::tuple_size<ArgsTuple>::value)>  > {
    using T = std::tuple_element_t<Start, ArgsTuple>;
    static constexpr std::size_t start = SearchPairs<T, Pairs...>::value,  // Searching through Pairs..., and will be 0 only if T is not found among the pairs.  Else we start after where the last T was found in Tuple.
        index = Find<Tuple, T, start>::value;
    using type = typename ObtainIndices<Tuple, ArgsTuple, Start+1, 
        std::index_sequence<Is..., index>, std::tuple<Pair<T, index+1>, Pairs...>>::type;
        // 'index+1' because we start searching for T again (if ever) after position 'index'.  Also, we must place Pair<T, index+1> before the Pairs... pack rather than after it because if a Pair with T already exists, that Pair must not be used again.
};

template <typename Tuple, typename F>
constexpr decltype(auto) searchArguments (Tuple&& t, F&& f) {
    using IndexSequence = typename ObtainIndices<std::decay_t<Tuple>, typename FunctionTraits<std::decay_t<F>>::args_type, 0, std::index_sequence<>, std::tuple<>>::type;
    return partial_apply(std::forward<Tuple>(t), std::forward<F>(f), IndexSequence{});
}

// Testing
int foo (int a, char c, bool b, int d, bool e, int f) {std::cout << "foo(" << a << ", " << c << ", " << b << ", " << d << ", " << e << ", " << f << ")\n";  return 8;}

int main() {
    const auto tuple = std::make_tuple(3.14, "bye", 5, true, 'a', 3.5, 20, false, 1000, 't', true, 5.8);
    std::cout << std::boolalpha;
    const int a = searchArguments(tuple, foo);  // foo(5, a, true, 20, false, 1000)
    std::cout << a << '\n';  // 8
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!