【问题标题】:Searching through a tuple for arguments of a function在元组中搜索函数的参数
【发布时间】: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) 被调用。 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&lt;N...&gt;,其中 N = 1 表示获得第一个,N = 2 表示获得第二,等等...?但随之而来的是更新最新的 N 值的责任。

【问题讨论】:

  • 我不明白。这不正是您的其他问题所涵盖的吗?
  • 这是后续是的,但是元组中的元素不连续。因此,如果通过元组搜索可能会获得重复,这是当前的问题。我以为我完成了任务,除了那部分。
  • 我明白了,所以参数必须属于同一类型?
  • 是的,这是我要处理的另一个曲线球。隐式转换是我不想要的。我想要完全匹配(不重复参数)。因此,我在当前的解决方案中使用了std::is_same。我确实从上一个问题中学到了很多东西(我什至在你的风格中使用命名空间),但重复问题似乎是一个非常棘手的问题。

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


【解决方案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.

【讨论】:

  • @ Columbo 很抱歉,您的算法有错误(或者您可能误解了规范)。您的测试在应该输出 5 a true 1000 时输出 5 a false 1000,因为 true 是 main() 中 tuple 中的第一个 bool 元素。我会尝试自己修复它(一旦我完全理解了你的代码)。此外,它不能在 GCC 5.2 中编译(我认为它不能正确计算某些 constexpr 模板语法),但我重写了你的代码以便它可以编译。
  • @ Columbo 你现在可能已经发现了,但是错误是obtain_indices&lt;find&lt;I, T, TupT&gt;::value+1,只有在之前的元组中找到T 时才应该使用它(例如,第一个int 元素在测试)。如果以前从未找到过T(例如测试中的bool 元素),我们需要从索引0 开始搜索。对我来说修复是什么并不明显,尽管我会尽力修复它。
  • @prestokeys 我正在修复 atm,忽略当前编辑。
  • @prestokeys 已修复。
【解决方案2】:

这是我发现的 Columbo 方法的替代方法。与其通过 args 元组向后搜索以确定之前是否已经搜索过 T,不如将​​ Pair&lt;T,index+1&gt; 存储在一个包中。然后,仅当在该包中的 Pairs 中未找到 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
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-01-20
    • 1970-01-01
    • 2021-03-07
    • 2021-06-03
    • 2019-08-14
    • 2016-05-24
    • 1970-01-01
    相关资源
    最近更新 更多