问题
Consider this
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);
}
So the arguments for foo
are first searched (from tuple
). Searching from left to right, the first int found is 5
, the first char found is a
, and the first bool found is true
. So then foo(5,a,true)
is called. Similarly for bar
and baz
. Except bar
takes 2 ints, and we don't want it to take 5
twice, but rather 5
and then 1000
. Similarly, baz
is to take (true, false)
for its arguments instead of (true, true)
.
My current solution below unfortunately outputs precisely what I just said should not be outputted:
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)
I realize that one possible (ugly) way to fix my current solution:
#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();
}
is to remove each element used from the tuple and pass the smaller tuple to the next recursion, thus guaranteeing that those repeat arguments don't occur. But that's a real mess (and probably unnecessarily inefficient). Furthermore, when calling up the next function, we need to restart with the original tuple again, and thus the original tuple must be passed around as well as each truncated tuple. I just want to ask if there is a better, much more elegant solution than this before I leap into this nightmarish task (if it even works at all).
Update: A new idea I thought of (if simply trying to fix my current solution), is to modify my getFirst
function to getN<N...>
, where N = 1 means get the first, N = 2 means get the second, etc...? But then there is the responsibility of updating the latest N value.
回答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{});
}
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.
回答2:
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 Pair
s 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
}
来源:https://stackoverflow.com/questions/36143359/searching-through-a-tuple-for-arguments-of-a-function