C ++ 17多参数包扩展

时间:2017-11-09 20:28:18

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

我正在尝试将函数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);

问题是在实现return语句中扩展参数包。我需要它在“内部”循环中展开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。

3 个答案:

答案 0 :(得分:4)

从这开始:

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的使用必须替换为decltype(I)::value中的get_element

答案 1 :(得分:1)

  

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

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

在我看来,你必须以相同的方式将两个包装分开并先扩展一个然后再扩展。

如果您看到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;
 }

答案 2 :(得分:1)

基于出色的解决方案,我开发了更通用的函数来转换和折叠(减少)元组。当您在问题中提到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);
}