如何在编译时从两个索引序列创建一个数组

问题描述 投票:6回答:5

(剧透 - 这是一个自我回答的问题)让我假装我有两个索引序列,例如using i1 = std::index_sequence<1, 3, 5, 7>;using i2 = std::index_sequence<2, 4, 6, 8>;

我想创建一个数组(在编译时),它将依次包含8个元素:1, 2, 3, 4, 5, 6, 7, 8,以便以下代码可以工作(比如,在全局范围内):

std::array<int, 8> arr = make_array(i1{}, i2{});

注意:如果我只想要一个序列,解决方案很简单:

template<size_t... Ix>
constexpr auto make_arr(std::index_sequence<Ix...> )
    return std::array{Ix...};
}

但是,如果我需要加入两个序列,那不是那么微不足道,例如,这不起作用:

template<size_t... Ix1, size_t... Ix2>
constexpr auto make_arr(std::index_sequence<Ix1...>, std::index_sequence<Ix2...>)
    return std::array{(Ix1, Ix2)...};
}

(上面的代码将只使用第二个序列中的值填充数组)。

另一个可能的解决方案是使用constexpr函数,它首先定义一个具有默认值的数组,然后将索引序列中的值复制到其中,但是当使用int时,这不适用于一些更精细的类型,这些类型不是default-constructible(显然,它们不是索引序列的一部分,但它们可能是其他的东西)。

是否有任何解决方案不需要循环和默认构造值?任何可用的C ++标准都是公平的游戏。

c++ c++17 variadic-templates template-meta-programming constexpr
5个回答
2
投票

使用一些实用程序从std::index_sequence中提取第一个数字,您可以:

template <typename Seq> struct pop_front;
template <typename Seq> using pop_front_t = typename pop_front<Seq>::type;
template <std::size_t I, std::size_t ... Is> struct pop_front<std::index_sequence<I, Is...>>
{
    using type = std::index_sequence<Is...>;
};

template <std::size_t I, std::size_t ... Is>
constexpr std::size_t get_front(std::index_sequence<I, Is...>) { return I; }

template <std::size_t ... Res, typename ... Seqs>
auto make_interleaved_seq(std::index_sequence<Res...>, Seqs...)
{
    if constexpr (((Seqs::size() == 0) && ...)) {
        return std::index_sequence<Res...>{};
    } else {
        static_assert(((Seqs::size() != 0) && ...), "Sequences don't have the same size");
        return make_interleaved_seq(std::index_sequence<Res..., get_front(Seqs{})...>{},
                                    pop_front_t<Seqs>{}...);
    }
}

template <std::size_t ... Is>
constexpr std::array<std::size_t, sizeof...(Is)> make_array(std::index_sequence<Is...>)
{
    return {{Is...}};
}

template <typename ... Seqs>
auto make_interleaved_array(Seqs... seqs)
{
    return make_array(make_interleaved_seq(std::index_sequence<>{}, seqs...));
}

Demo


1
投票

到目前为止,我知道两种解决方案。

在第一个中,我设法使用C ++折叠表达式和运算符重载。这段代码很糟糕,我并不为此感到自豪 - 但它就在那里。欢迎大家发表评论并做出贡献:

#include <utility>
#include <array>

struct Int {
    size_t i;
};

constexpr std::array<Int, 2> operator+(Int i1, Int i2) {
    return {i1, i2};
}

template<size_t SZ, size_t... Ix>
constexpr auto concat(std::array<Int, 2> arr1, std::array<Int, SZ> arr2, std::index_sequence<Ix...>)
{
    return std::array<Int, SZ+2>{arr1[0], arr1[1], arr2[Ix]...};
}

template<size_t SZ>
constexpr auto operator+ (std::array<Int, 2> arr1, std::array<Int, SZ> arr2) {
    return concat(arr1, arr2, std::make_index_sequence<SZ>{});
}


template<size_t SZ, size_t... Ix>
constexpr auto convert_impl(std::array<Int, SZ> arr, std::index_sequence<Ix...>) {
    return std::array{arr[Ix].i...};
}

template<size_t SZ>
constexpr auto convert(std::array<Int, SZ> arr) {
    return convert_impl(arr, std::make_index_sequence<SZ>{});
}

template<size_t... IX1, size_t... IX2>
constexpr auto make_arr(std::index_sequence<IX1...>, std::index_sequence<IX2...>) {

    return convert(((Int{IX1} + Int{IX2})+ ...));
}

using i1 = std::index_sequence<1, 3, 5, 7>;
using i2 = std::index_sequence<2, 4, 6, 8>;
auto k = make_arr(i1{}, i2{});

第二种解决方案是更好的方法:

#include <utility>
#include <array>

template<size_t... Ix, class T, size_t SZ>
auto concat(T t1, T t2, std::array<T, SZ> arr, std::index_sequence<Ix...>) {
    return std::array{t1, t2, arr[Ix]...};
}

template<size_t i0, size_t... Ix0, size_t i1, size_t... Ix1>
auto make_array(std::index_sequence<i0, Ix0...>, std::index_sequence<i1, Ix1...>) {

    if constexpr (sizeof...(Ix0) > 0) {
        return concat(i0, 
                      i1,
                      make_array(std::index_sequence<Ix0...>{}, std::index_sequence<Ix1...>{}),
                      std::make_index_sequence<sizeof...(Ix0) + sizeof...(Ix1)>{}
                     );
    } else {
        return std::array{i0, i1};
    }
}

using i1 = std::index_sequence<1, 3, 5, 7>;
using i2 = std::index_sequence<2, 4, 6, 8>;
std::array<size_t, 8> k = make_array(i1{}, i2{});

1
投票

怎么样与索引一起玩(移位,模数......这类事情)?

#include <array>
#include <iostream>
#include <type_traits>

template <typename T, std::size_t ... Is>
constexpr auto make_arr_helper (T const & arr0,
                                std::index_sequence<Is...>)
 { 
   constexpr auto  DD2 = sizeof...(Is) >> 1;

   return std::array{arr0[(Is>>1)+(Is%2 ? DD2 : 0u)]...};
 }

template <std::size_t ... IX1, std::size_t ... IX2>
constexpr auto make_arr (std::index_sequence<IX1...>,
                         std::index_sequence<IX2...>)
 {
   static_assert( sizeof...(IX1) == sizeof...(IX2) );

   return make_arr_helper(std::array{IX1..., IX2...},
                          std::make_index_sequence<(sizeof...(IX1)<<1)>{});
 }

int main ()
 {
   using i1 = std::index_sequence<1, 3, 5, 7>;
   using i2 = std::index_sequence<2, 4, 6, 8>;

   constexpr auto k = make_arr(i1{}, i2{});

   for ( auto const & i : k )
      std::cout << i << ' ';

   std::cout << std::endl;
 }

- 编辑 -

OP问道

但是,如果你想合并其中3个怎么办? 4?班次/模块是否有效?

不移位(在2种情况下是乘法和除法的简化为2)但是,使用乘法和除法,有效。

案例3中的make_arr_helper()很简单

template <typename T, std::size_t ... Is>
constexpr auto make_arr_helper (T const & arr0,
                                std::index_sequence<Is...>)
 { 
   constexpr auto  DD3 = sizeof...(Is) / 3u;

   return std::array{arr0[(Is/3u)+((Is%3) * DD3)]...};
 }

并且将序列的数量作为参数传递,可以很容易地推广。

以下是完整案例3的示例

#include <array>
#include <iostream>
#include <type_traits>

template <typename T, std::size_t ... Is>
constexpr auto make_arr_helper (T const & arr0,
                                std::index_sequence<Is...>)
 { 
   constexpr auto  DD3 = sizeof...(Is) / 3u;

   return std::array{arr0[(Is/3u)+((Is%3) * DD3)]...};
 }

template <std::size_t ... IX1, std::size_t ... IX2,
          std::size_t ... IX3>
constexpr auto make_arr (std::index_sequence<IX1...>,
                         std::index_sequence<IX2...>,
                         std::index_sequence<IX3...>)
 {
   static_assert( sizeof...(IX1) == sizeof...(IX2) );
   static_assert( sizeof...(IX1) == sizeof...(IX3) );

   return make_arr_helper(std::array{IX1..., IX2..., IX3...},
                          std::make_index_sequence<(sizeof...(IX1)*3u)>{});
 }

int main ()
 {
   using i1 = std::index_sequence<1, 4, 7, 10>;
   using i2 = std::index_sequence<2, 5, 8, 11>;
   using i3 = std::index_sequence<3, 6, 9, 12>;


   constexpr auto k = make_arr(i1{}, i2{}, i3{});

   for ( auto const & i : k )
      std::cout << i << ' ';

   std::cout << std::endl;
 }

0
投票

这似乎是一个有趣的挑战,但目的并不完全清楚。特别是,我不明白你的解释中的“别的东西”是什么意思:

另一个可能的解决方案[...]不适用于一些更复杂的类型,它们不是默认构造的(显然,它们不是索引序列的一部分,但它们可能是其他的东西)。

您能举例说明所需技术的要求吗?如果要求是“无循环,没有默认构造”,那么解决方案可能是std::forward_as_tuple,然后是std::tuple_cat,然后是“detie into array”:

#include <cstddef>

#include <array>
#include <iostream>
#include <tuple>
#include <type_traits>
#include <utility>

template<std::size_t... gs, class T0, class... Ts>
constexpr std::array<std::decay_t<T0>, 1+sizeof...(Ts)> detie_into_array(
  std::index_sequence<gs...>,
  const std::tuple<T0&&, Ts&&...>& tuple// TODO: think about the type deduction
) {
  static_assert(
    std::is_same<
      std::index_sequence<gs...>,
      std::make_index_sequence<1+sizeof...(Ts)>
    >{}
  );

  static_assert(
    std::conjunction<
      std::is_same<std::decay_t<T0>, T0>,
      std::is_same<std::decay_t<T0>, Ts>...
    >{}
  );

  return {std::get<gs>(tuple)...};
}



template<int... is, int... js>
constexpr auto make_array(
  std::integer_sequence<int, is...>,
  std::integer_sequence<int, js...>
) {
  static_assert(sizeof...(is) == sizeof...(js), "How to interleave otherwise?");

  using GlobalSeq = std::make_index_sequence<sizeof...(is) + sizeof...(js)>;

  return detie_into_array(
    GlobalSeq{},
    std::tuple_cat(
      std::forward_as_tuple(is, js)...// TODO: think about the type deduction
    )// NOTE: first idea was `std::tie`, but that is based on lvalue refs
  );
}

////////////////////////////////////////////////////////////////////////////////

template<class T>
void inspect(const T&) {
  std::cout << __PRETTY_FUNCTION__ << std::endl;
}

int main() {
  using i1 = std::integer_sequence<int, 1, 3, 5, 7>;
  using i2 = std::integer_sequence<int, 2, 4, 6, 8>;

  auto arr = make_array(i1{}, i2{});

  inspect(arr);
  for(auto&& i : arr) {
    std::cout << i << std::endl;
  }
}

0
投票

在现代C ++中,我总是更喜欢constexpr正常编程而不是模板元编程。

不幸的是,C ++算法还不是constexpr。所以,我们必须重新实现它们:

#include<array>
#include<utility>
#include<algorithm>

template<std::size_t... Ix1, std::size_t... Ix2>
constexpr auto make_array(std::index_sequence<Ix1...>,std::index_sequence<Ix2...>)
{
    const auto a1 = std::array{Ix1...};
    const auto a2 = std::array{Ix2...};
    constexpr std::size_t N1 = a1.size();
    constexpr std::size_t N2 = a2.size();

    std::array<std::size_t,N1+N2> result{};   // (a)
    // std::merge(a1.begin(), a1.end(), a2.begin(), a2.end(), result.begin());
    // (b)
    std::size_t i=0, j=0, k=0;
    while (k<N1+N2)
    {
        if(i<N1 && (j==N2||a1[i] < a2[j]))
            { result[k] = a1[i]; ++k; ++i; }
        else
            { result[k] = a2[j]; ++k; ++j; }
    }
    // end of (b)
    return result;
}

using i1 = std::index_sequence<1, 3, 5, 7>; 
using i2 = std::index_sequence<2, 4, 6, 8>;


int main() {
    constexpr auto a = make_array(i1{},i2{});
    // ...
}

如果std::mergeconstexpr,那么函数join_arr是一个5-liner。

如果您不知道i1i2已经排序,您需要实现自己的constexpr版本的std::sort

如果你想只是交替组合索引序列,你可以使用类似的方法

    if (N1!=N2) throw std::logic_error("The index sequences need to have the same length.");
    for (std::size_t i=0; i<N1; ++i)
    {
        result[2*i  ] = a1[i];
        result[2*i+1] = a2[i];
    }

而不是(b)。只要你没有扔掉,throw声明就没问题了。 (因此,异常被转换为编译时错误。这正是您想要的。)

最后,如果类型不是默认可构造的,您可以编写

std::array<T,N1+N2> result{a1[0]};

而不是(a),即你用一些随机实例填充你的结果。这只需要可复制的构建(所有提出的解决方案都需要)。

© www.soinside.com 2019 - 2024. All rights reserved.