【问题标题】:How can I map a C++ parameter pack into a sequence of std::pair objects?如何将 C++ 参数包映射到 std::pair 对象序列?
【发布时间】:2017-01-24 19:51:37
【问题描述】:

我有一个可变参数模板函数foo()

template <typename... Args>
void foo(Args &&... args);

此函数旨在使用size_t 的所有参数调用。我可以使用some metaprogramming 来强制执行。我需要一次取出两个参数的结果列表并将它们放入std::pair&lt;size_t, size_t&gt; 的容器中。从概念上讲,类似于:

std::vector<std::pair<size_t, size_t> > = { 
    std::make_pair(args[0], args[1]), 
    std::make_pair(args[2], args[3]), ...
};

有没有一种简单的方法可以做到这一点?我知道通过包扩展,我可以将参数放入一个平面容器中,但是有没有办法将它们两两分组到 std::pair 对象中?

【问题讨论】:

  • "有直接的方法吗?"没有。
  • 我猜,可以创建一个递归插入器,它会消耗两个参数,并将它们作为对推入向量中,并具有两个停止案例特化。
  • 您是否可以转发到具有额外 std::index_sequence&lt;I...&gt; 参数的内部辅助函数?
  • @KerrekSB:这是可行的,但我仍然习惯于使用 C++11 功能,所以我不确定它是如何工作的。 std::index_sequence&lt;&gt; 也仅限于 C++14,不是吗?不是炫耀,但我想知道它。
  • @JasonR:你可以自己定义任何等同于index_sequence的模板。

标签: c++ c++11


【解决方案1】:

对包进行索引实际上并不可行(还没有?),但对元组进行索引是可行的。只需先将所有内容都放入一个元组中,然后再将所有内容拉出即可。由于所有内容都是size_t,我们可以直接复制:

template <size_t... Is, class Tuple>
std::vector<std::pair<size_t, size_t>> 
foo_impl(std::index_sequence<Is...>, Tuple tuple) {
    return std::vector<std::pair<size_t, size_t> >{ 
        std::make_pair(std::get<2*Is>(tuple), std::get<2*Is+1>(tuple))...
    };
}

template <typename... Args>
void foo(Args... args)
{
    auto vs = foo_impl(std::make_index_sequence<sizeof...(Args)/2>{},
        std::make_tuple(args...));

    // ...
}

【讨论】:

  • 甚至std::forward_as_tuple?
  • @KerrekSB 他们都是size_ts。很确定编译器无论如何都会忽略tuple
【解决方案2】:

假设你被允许将你的逻辑重构为一个内部辅助函数:

template <typename ...Args>
void foo(Args &&... args)
{
    foo_impl(std::make_index_sequence<sizeof...(Args) / 2>(),
             std::forward<Args>(args)...);
}

现在我们可以按索引对参数包索引进行操作:

template <std::size_t ...I, typename ...Args>
void foo_impl(std::index_sequence<I...>, Args &&... args)
{
    std::vector<std::pair<std::size_t, std::size_t>> v =
        { GetPair(std::integral_constant<std::size_t, I>(), args...)... };
}

还需要实现对提取器:

template <typename A, typename B, typename ...Tail>
std::pair<std::size_t, std::size_t> GetPair(std::integral_constant<std::size_t, 0>,
                                            A a, B b, Tail ... tail)
{
    return { a, b };
}

template <std::size_t I, typename A, typename B, typename ...Tail>
std::pair<std::size_t, std::size_t> GetPair(std::integral_constant<std::size_t, I>,
                                            A a, B b, Tail ... tail)
{
    return GetPair<I - 1>(tail...);
}

【讨论】:

  • 优秀。我接受了 Barry 的回答,因为它有点简短,但这看起来也是一个不错的解决方案。
  • @JasonR:是的,std::get 适用于元组,就像我的 GetPair 适用于原始包一样。对于这种泛型代码,元组非常强大。
【解决方案3】:

range-v3,你可以这样做

template <typename... Args>
void foo(Args&&... args)
{
    std::initializer_list<std::size_t> nbs = {static_cast<std::size_t>(args)...};
    const auto pair_view =
        ranges::view::zip(nbs | ranges::view::stride(2),
                          nbs | ranges::view::drop(1) |  ranges::view::stride(2));

    // And possibly
    std::vector<std::pair<std::size_t, std::size_t>> pairs = pair_view;
    // ...
}

Demo

【讨论】:

  • 范围很漂亮 :)
  • 最后一行可以修改为:auto pairs = pair_view |转换({返回 std::make_pair(...);}) |范围::to_vector;
  • 我非常喜欢这个。我最初是按照这些思路思考的,但我必须等到范围进入标准!
【解决方案4】:

有人(咳嗽 @Barry cough)说无法对包进行索引。

这是 C++。不可能意味着我们还没有写出来。

template<std::size_t I> struct index_t:std::integral_constant<std::size_t, I> {
  using std::integral_constant<std::size_t, I>::integral_constant;
  template<std::size_t J>
  constexpr index_t<I+J> operator+( index_t<J> ) const { return {}; }
  template<std::size_t J>
  constexpr index_t<I-J> operator-( index_t<J> ) const { return {}; }
  template<std::size_t J>
  constexpr index_t<I*J> operator*( index_t<J> ) const { return {}; }
  template<std::size_t J>
  constexpr index_t<I/J> operator/( index_t<J> ) const { return {}; }
};
template<std::size_t I>
constexpr index_t<I> index{};

template<std::size_t B>
constexpr index_t<1> exponent( index_t<B>, index_t<0> ) { return {}; }

template<std::size_t B, std::size_t E>
constexpr auto exponent( index_t<B>, index_t<E> ) {
  return index<B> * exponent( index<B>, index<E-1> );
}
template<std::size_t N>
constexpr index_t<0> from_base(index_t<N>) { return {}; }
template<std::size_t N, std::size_t c>
constexpr index_t<c-'0'> from_base(index_t<N>, index_t<c>) { return {}; }
template<std::size_t N, std::size_t c0, std::size_t...cs>
constexpr auto from_base(index_t<N>, index_t<c0>, index_t<cs>...) {
  return 
    from_base(index<N>, index<c0>) * exponent(index<N>, index<sizeof...(cs)>)
    + from_base(index<N>, index<cs>...)
  ;
}

template<char...cs>
constexpr auto operator""_idx(){
  return from_base(index<10>, index<cs>...);
}

auto nth = [](auto index_in){
  return [](auto&&...elems)->decltype(auto){
    using std::get;
    constexpr auto I= index<decltype(index_in){}>;
    return get<I>(std::forward_as_tuple(decltype(elems)(elems)...));
  };
};

现在我们得到:

using pair_vec = std::vector<std::pair<std::size_t, std::size_t>>;
template <typename... Args>
pair_vec foo(Args &&... args) {
  return
    index_over< sizeof...(args)/2 >()
    ([&](auto...Is)->pair_vec{
      return {
        {
          nth( Is*2_idx )( decltype(args)(args)... ),
          nth( Is*2_idx+1_idx )( decltype(args)(args)... )
        }...
      };
    });
}

我们使用编译时常量索引“直接”索引到我们的参数包中。

live example.

【讨论】:

  • 非常令人印象深刻。我比现代 C++ 曲线落后了几年,所以我离能够从头开始构想出这样的解决方案还有很长的路要走。很好的示范。
猜你喜欢
  • 2020-08-10
  • 2013-07-29
  • 1970-01-01
  • 1970-01-01
  • 2012-04-16
  • 2010-09-10
  • 1970-01-01
  • 1970-01-01
  • 2022-01-01
相关资源
最近更新 更多