C++17 多参数包扩展

C++17 multiple parameter pack expansion

本文关键字:扩展 包扩展 参数 C++17      更新时间:2023-10-16

我正在尝试将函数f映射到元组t0t1等上以返回元组 std::tuple<f(std::get<0>(t0),std:get<0>(t1),...),f(std::get<1>(t0),std::get<1>(t1),...),...) .我有一个使用 carcdrcons 的版本,但我试图让一个使用 std::index_sequence 的版本工作。

代码:

// Helper
template<typename T>
using make_tuple_index = std::make_index_sequence<std::tuple_size<T>::value>;
// Implementation
template<typename F, typename... Ts, std::size_t... Is>
auto mapx_n_impl(const F& f, std::index_sequence<Is...>, const Ts&... t)
{ return std::make_tuple(f(std::get<Is>(t...))...); }
// Interface
template<typename T,
         typename F,
         typename Indices = make_tuple_index<T>>
auto map(const T& t, const F& f)
{ return mapx_impl(t, f, Indices{}); }
// Test
auto tup1 = std::make_tuple(1.0, 2.0, 3.0);
auto tup2 = std::make_tuple(0.0, 1.0, 2.0);
auto r = mapx_n([](auto x, auto y) { return x - y; }, tup1, tup2);

问题是扩展实现返回语句中的参数包。我需要它在"内部"循环中扩展t,在"外部"循环中Is。如何控制扩张?而且,如何修复我的退货声明?

更新:

根据@Yakk的回应和@max66的进一步阐述,我尽可能地简化了我的代码。当前版本集成了@Yakk答案中的参数包扩展帮助程序的一个版本,并将get_element调用分解为 lambda。

// invoke_with_pack
template<std::size_t... Is, typename F>
auto invoke_with_pack(std::index_sequence<Is...>, F&& function)
{ return function(std::integral_constant<std::size_t, Is>{}...); }
// nth
template<natural N, typename... Ts>
using nth = typename std::tuple_element<N, std::tuple<Ts...>>::type;
// make_tuple_index -- Helper template for computing indices
// corresponding to a tuple.
template<typename T>
using make_tuple_index = std::make_index_sequence<std::tuple_size<T>::value>;
// map_n -- Map <function> over <tuples> t0,t1,...
template<typename F,
         typename... Ts,
         typename Indices = make_tuple_index<nth<0,Ts...>>>
auto map_n(F&& function, Ts&... tuples)
{
    auto get_element = [&](auto I) { return function(std::get<I>(tuples)...); };
    return invoke_with_pack(Indices{}, [&](auto... Is) {
            return std::make_tuple(get_element(Is)...);
        });
}

现在弄清楚如何使用索引而不是 car、cdr 和缺点来实现fold_left和fold_right。

从这个开始:

namespace utility {
  template<std::size_t...Is>
  auto index_over( std::index_sequence<Is...> ) {
    return [](auto&& f)->decltype(auto) {
      return decltype(f)(f)( std::integral_constant<std::size_t, Is>{}... );
    };
  }
  template<std::size_t N>
  auto index_upto( std::integral_constant<std::size_t, N> ={} ) {
    return index_over( std::make_index_sequence<N>{} );
  }
}

这让我们不必仅仅为了扩展一些参数包而编写一大堆函数。 index_upto<7>()([](auto...Is){ /* here */ })为您提供了一个上下文,其中您在包中具有一堆编译时整数常量 0 到 6。

template<class F, class T0, class...Tuples>
auto map_over_tuples( F&& f, T0&... t0, Tuples&&... tuples ) {
  using tuple_size = typename std::tuple_size< std::decay_t<T0> >::type;
  auto get_element = [&](auto I){
    return f(std::get<I>(std::forward<T0>(t0)), std::get<I>(std::forward<Tuples>(tuples)...));
  };
  return index_upto<tuple_size{}>()([&](auto...Is){
    return std::make_tuple( get_element(Is)... );
  });
}

在某些编译器中,I的使用必须替换为 get_element 中的 decltype(I)::value

问题是扩展实现返回语句中的参数包。我需要它在"内部"循环中扩展 t 并在"外部"循环中扩展。如何控制扩张?而且,如何修复我的退货声明?

我没有看到一种简单而优雅的方法可以做到这一点。

在我看来,您必须以相同的方式将两个包解耦,然后先扩展一个,然后再扩展另一个。

如果你看到 Yakk 解决方案,你会看到内部扩展 ( t... ( 通过一个包含单个调用f()的 lambda 函数。

下面是一个解决方案,基于与模板函数相同的原理,并使用std::applyf()的调用留在外面。

坦率地说,我认为 Yakk 解决方案更有效(不需要创建无用的元组(,所以把这个例子当作一个奇怪

的例子
#include <tuple>
#include <iostream>
template <std::size_t I, typename ... Ts>
auto getN (Ts const & ... t)
 { return std::make_tuple(std::get<I>(t)...); }
template<typename F, typename... Ts, std::size_t... Is>
auto mapx_n_impl(const F& f, std::index_sequence<Is...>, const Ts&... t)
{ return std::make_tuple(std::apply(f, getN<Is>(t...))...); }
template <typename F, typename T0, typename ... Ts>
auto mapx_n (F const & f, T0 const & t0, Ts const & ... ts)
{ return mapx_n_impl(f,
     std::make_index_sequence<std::tuple_size<T0>::value> {}, t0, ts...); }
int main ()
 {
   // Test
   auto tup1 = std::make_tuple(1.0, 2.0, 3.0);
   auto tup2 = std::make_tuple(0.0, 1.0, 2.0);
   auto r = mapx_n([](auto x, auto y) { return x - y; }, tup1, tup2);
   std::cout << std::get<0U>(r) << std::endl;
   std::cout << std::get<1U>(r) << std::endl;
   std::cout << std::get<2U>(r) << std::endl;
 }

基于伟大的解决方案,我开发了一个更通用的函数来转换和折叠(减少(元组。正如您在问题中提到fold_leftfold_right时,这可能会引起讨论的兴趣。

基本思想是将第二个函子应用于映射(也称为转换(元组,而不是像在解决方案中那样调用std::make_tuple。这允许许多算法(例如 count_ifall_ofany_of等(易于实施。

活生生的例子在这里。

#include <tuple>
#include <functional>
#define FWD(x) std::forward<decltype(x)>(x)
namespace tuple_utils {
    template<class UnaryFunc, std::size_t... Idx>
    constexpr auto apply_for_each_index(std::index_sequence<Idx...>, UnaryFunc&& f) {
        return FWD(f)(std::integral_constant<std::size_t, Idx>{}...);
    }
    template<typename T>
    using make_tuple_index = std::make_index_sequence<std::tuple_size<std::decay_t<T>>::value>;
    template<class... Ts>
    using first_element_t =  typename std::tuple_element<0, std::tuple<Ts...>>::type;
    template<class T>
    constexpr size_t tuple_size_v = std::tuple_size_v<std::decay_t<T>>;
    template<class Map, class Reduce, class... Tuples>
    constexpr auto
    transform_reduce(Map &&transform_func, Reduce &&reduce_func, Tuples&&... tuples) {
        using first_tuple_t = first_element_t<Tuples...>;
        constexpr size_t first_tuple_size = tuple_size_v<first_tuple_t>;
        static_assert(((tuple_size_v<Tuples> == first_tuple_size) && ...), "all tuples must be of same size!");
        auto transform_elements_at = [&](auto Idx){
            return FWD(transform_func)(std::get<Idx>(FWD(tuples))...);
        };
        using Indices = make_tuple_index<first_tuple_t>;
        return apply_for_each_index(
            Indices{},
            [&](auto... Indices) {
                return FWD(reduce_func)(transform_elements_at(Indices)...);
            }
        );
    }
}
int main()
{   
    using tuple_utils::transform_reduce;
    auto make_tuple = [](auto&&... xs) { return std::make_tuple(FWD(xs)...); };
    auto equal = [](auto&& first, auto&&... rest){return ((FWD(first) == FWD(rest)) && ... ); };
    constexpr auto all = [](auto... bs) { return (bs && ...);};
    constexpr auto any = [](auto... bs) { return (bs || ...);};
    constexpr auto count = [](auto... bs) { return (bs + ...); };
    static_assert(transform_reduce(std::equal_to<>(), make_tuple, std::tuple{1,2,3,4}, std::tuple{1,2,7,8}) == std::tuple{true, true, false, false});
    static_assert(transform_reduce(equal, all, std::tuple{1,2,3,4}, std::tuple{1,2,7,8}) == false);
    static_assert(transform_reduce(equal, all, std::tuple{1,2,3,4}, std::tuple{1,2,3,4}, std::tuple{1,2,3,4}) == true);
    static_assert(transform_reduce(equal, any, std::tuple{1,2,3,4}, std::tuple{1,2,7,8}) == true);
    static_assert(transform_reduce(equal, count, std::tuple{1,2,3,4}, std::tuple{1,2,7,8}) == 2);
}