在元组中搜索函数的参数
Searching through a tuple for arguments of a function
考虑一下
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)
就被调用了。对于 bar
和 baz
也是如此。除了 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值的责任了。
#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.
这是我找到的替代 Columbo 方法的方法。不是通过 args 元组向后搜索以确定之前是否搜索过 T
,而是将 Pair<T,index+1>
存储在一个包中。然后,仅当在该包的 Pair
中找不到 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
}
考虑一下
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)
就被调用了。对于 bar
和 baz
也是如此。除了 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值的责任了。
#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.
这是我找到的替代 Columbo 方法的方法。不是通过 args 元组向后搜索以确定之前是否搜索过 T
,而是将 Pair<T,index+1>
存储在一个包中。然后,仅当在该包的 Pair
中找不到 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
}