c++ - 在元组中搜索函数的参数

标签 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 , 找到的第一个字符是 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值的责任了。

最佳答案

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

关于c++ - 在元组中搜索函数的参数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36143359/

相关文章:

python - python中不同元组的元素的并集

c# - 如何避免丢失列表的值

C++ 模板化函数调用托管函数

c++ - 如何在 C++ 中简化这个 "variable as template parameter"?

python - 为什么 Python 中没有元组理解?

c++ - OpenMP 线程创建

C++ - 具有多种参数类型的方法

c++ - 为什么 std::numeric_limits<long long>::max() 会失败?

c++ - 未找到“initializer_list”文件

c++ - Hippo Mocks 中具有不同返回值的多个预期调用是否可以重复使用模拟?