【问题标题】:C++17 multiple parameter pack expansionC++17多参数包扩展
【发布时间】:2018-10-13 21:36:13
【问题描述】:

我正在尝试将函数 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 和 cons 来实现 fold_left 和 fold_right。

【问题讨论】:

    标签: c++ templates variadic-templates c++17


    【解决方案1】:

    从这里开始:

    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&lt;7&gt;()([](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

    【讨论】:

    • 对参数包进行排序的有趣方法——我将在某个时候尝试一下。根据您的回答,我能够想出一个可行的解决方案,类似于下面 max66 的回答。
    【解决方案2】:

    问题在于扩展实现返回语句中的参数包。我需要它在“内部”循环中扩展 t 并在“外部”循环中扩展 Is。扩容如何控制?还有,如何修复我的退货声明?

    我没有看到一种简单而优雅的方式来做到这一点。

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

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

    以下是一个解决方案,基于相同的原理与模板函数,并使用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;
     }
    

    【讨论】:

    • 这清楚地表明了挑战。基于 Yakk 的解决方案,我最终得到了与此解决方案类似的东西——只是因为它与我已经解决问题的方式非常吻合。
    【解决方案3】:

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

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

    现场示例here

    #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);
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-10-06
      • 2021-01-05
      • 1970-01-01
      • 2023-03-10
      • 1970-01-01
      相关资源
      最近更新 更多