【发布时间】:2016-03-21 23:12:23
【问题描述】:
考虑一下
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 值的责任。
【问题讨论】:
-
我不明白。这不正是您的其他问题所涵盖的吗?
-
这是后续是的,但是元组中的元素不连续。因此,如果通过元组搜索可能会获得重复,这是当前的问题。我以为我完成了任务,除了那部分。
-
我明白了,所以参数必须属于同一类型?
-
是的,这是我要处理的另一个曲线球。隐式转换是我不想要的。我想要完全匹配(不重复参数)。因此,我在当前的解决方案中使用了
std::is_same。我确实从上一个问题中学到了很多东西(我什至在你的风格中使用命名空间),但重复问题似乎是一个非常棘手的问题。
标签: c++ templates tuples c++14 template-meta-programming