在元组中搜索函数的参数

时间:2016-03-21 23:12:24

标签: c++ templates tuples c++14 template-meta-programming

考虑一下

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

int main() {
    const auto tuple = std::make_tuple(5, true, 'a', 3.5, false, 1000, 't', 2, true, 5.8);
    const std::tuple<int, double, char> t = searchArguments (tuple, foo, bar, baz);
}

首先搜索foo的参数(来自tuple)。从左到右搜索,找到的第一个int是5,找到的第一个char是a,找到的第一个bool是true。所以然后调用foo(5,a,true)。同样适用于barbaz。除了bar需要2个整数,我们不希望它花费5两次,而是5然后1000。同样,baz(true, false)用于其参数,而不是(true, true)

我的当前解决方案遗憾地输出了我刚才所说的不应该输出的内容:

foo(5,a,true)  // OK
bar(5,a,true,5)  // Nope, we want bar(5,a,true,1000)
baz(true,true)  // Nope, we want baz(true,false)

我意识到修复当前解决方案的一种可能(丑陋)方式:

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

// C++17 std::apply
template <typename F, typename Tuple, size_t... Is>
auto apply_impl (F&& f, Tuple&& tuple, const std::index_sequence<Is...>&) {
    return (std::forward<F>(f))(std::get<Is>(std::forward<Tuple>(tuple))...);
}

template <typename F, typename Tuple>
auto apply (F&& f, Tuple&& tuple) {  // Invoke the Callable object f with a tuple of arguments. 
    return apply_impl(std::forward<F>(f), std::forward<Tuple>(tuple), std::make_index_sequence<std::tuple_size<std::decay_t<Tuple>>::value>());
}

// 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<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.

namespace getFirstDetail {
    template <typename T, typename Tuple, std::size_t N, bool>
    struct SearchTuple : SearchTuple<T, Tuple, N+1, std::is_same<std::tuple_element_t<N+1, Tuple>, T>::value> {};

    template <typename T, typename Tuple, std::size_t N>
    struct SearchTuple<T, Tuple, N, true> {
        static T search (const Tuple& tuple) {return std::get<N>(tuple);}
    };
}

// Get the first element of a tuple whose type is T.  Note that using std::get<T> will not suffice since this fails to compile if the tuple has more than one element of type T.
// It is the client's responsiblity to ensure that such an element in the tuple exists (else there will be a crash).
template <typename T, typename Tuple>
T getFirst (const Tuple& tuple) {
    return getFirstDetail::SearchTuple<T, Tuple, -1, false>::search(tuple);
}

namespace searchArgumentsDetail {   
    template <typename> struct Search;

    template <typename... Args>
    struct Search<std::tuple<Args...>> {
        template <typename R, typename Tuple, typename F>
        static R execute (const Tuple& tuple, F f) {return apply(f, std::make_tuple(getFirst<Args>(tuple)...));}
    };
}

template <typename Tuple>
std::tuple<> searchArguments (const Tuple&) {return std::tuple<>();}

// Gathers the first possible elements from 'tuple' that 'f' can accept (reading from left to right) and carries out the function.  Then it is repeated for the remaining functions fs...
template <typename Tuple, typename F, typename... Fs>
auto searchArguments (const Tuple& tuple, F f, Fs... fs) {
    using ArgsType = typename FunctionTraits<F>::args_type;
    using ReturnType = typename FunctionTraits<F>::return_type;
    const auto singleTuple = std::make_tuple (searchArgumentsDetail::Search<ArgsType>::template execute<ReturnType>(tuple, f));
    return std::tuple_cat (singleTuple, searchArguments (tuple, fs...));
}

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

int main() {
    const auto tuple = std::make_tuple(5, true, 'a', 3.5, false, 1000, 't', 2, true, 5.8);
    std::cout << std::boolalpha;
    const std::tuple<int, double, char> t = searchArguments (tuple, foo, bar, baz);
    std::cout << std::get<0>(t) << ' ' << std::get<1>(t) << ' ' << std::get<2>(t) << '\n';  // 8 2.5 a
    std::cin.get();
}

是删除元组中使用的每个元素,并将较小的元组传递给下一个递归,从而保证不会发生那些重复的参数。但那真是一团糟(而且可能是不必要的低效率)。此外,在调用下一个函数时,我们需要再次使用原始元组重新启动,因此必须传递原始元组以及每个截断的元组。我只是想问一下,在我进入这个噩梦般的任务之前是否有比这更好,更优雅的解决方案(如果它甚至可以工作的话)。

更新:我想到的一个新想法(如果只是尝试修复我当前的解决方案),就是将我的getFirst函数修改为getN<N...>,其中N = 1表示获得第一个,N = 2意味着得到第二个......等等?但是,有责任更新最新的N值。

2 个答案:

答案 0 :(得分:1)

#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{});
}

从你的例子:

#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

答案 1 :(得分:0)

这是我发现Columbo方法的另一种选择。而不是向前搜索args元组以确定之前是否已搜索T,而是将Pair<T,index+1>存储在一个包中。然后,只有在该包中的Pair s中找不到T,否则搜索从位置0开始,否则在index+1位置。我不知道哪种方法更有效率。

#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
}