如何在编译时从两个索引序列创建一个数组
How to create an array from two index sequence at compile time
(扰流板 - 这是一个自我解决的问题)让我们假装我有两个索引序列,例如using i1 = std::index_sequence<1, 3, 5, 7>;
和using i2 = std::index_sequence<2, 4, 6, 8>;
我想制作一个数组(at compile 时间),该数组中的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可以使用,但它与一些更精致的类型无法使用,不是默认构造(显然,它们不是索引序列的一部分,但它们可能是其他的)。
是否有任何不需要循环和默认构造值的解决方案?任何可用的C 标准都是公平的游戏。
在某些实用程序中提取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
到目前为止,我知道两个解决方案。
在第一个中,我设法使用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{});
与索引的小戏剧(Shift,Modulus ...此类事物)?
#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的简化),但是,使用乘法和划分,作品。
make_arr_helper()
对于情况3,很简单
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;
}
这似乎是一个有趣的挑战,但目的对我来说并不完全清楚。特别是,我不明白您的解释中的"其他事物"的含义:
另一个潜在的解决方案[...]再也无法使用 精心设计的类型,这些类型不是默认构造的(显然,它们 不会成为索引序列的一部分,,但它们可能是某物 else )。
您可以举个例子来证明所需技术的要求吗?如果要求是"没有循环,没有默认结构",则可以是std::forward_as_tuple
,然后是std::tuple_cat
,然后是" Detie Into In in 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;
}
}
在现代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::merge
为constexpr
,则功能join_arr
是5件函数。
如果您不知道i1
和i2
已排序,则需要实现自己的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),即,您用一些随机实例填充结果。这只需要构造复制(所有提出的解决方案都需要)。
- 在将 new 与指针一起使用时,创建数组的指定长度
- 在函数中使用 const int size 参数创建数组会在 Visual Studio 中抛出错误 C++:表达式的计
- 合并排序不排序自创建数组类 c++
- 基于参数创建数组
- 使用new创建数组而不声明大小
- c++用输入数据创建数组
- 从类 c++ 动态创建数组
- 如何使用 void 函数从类创建数组
- 未知大小的数组作为类成员,用于在运行时(对象创建时间)创建数组的对象
- 如何在不知道Arduino中知道其大小的情况下创建数组和存储值
- 使用动态分配创建数组并将元素插入其中
- 是否可以使用C 函数中的const int参数创建数组
- 如何在C++函数中创建数组
- 使用 ::std::vector 创建数组
- 在编译时以增量方式创建数组
- Numpy C API - 使用 PyArray_Descr 创建数组会导致段错误
- 为什么当我尝试在类中创建数组时"invalid use of non-static data member"我会收到错误?
- 取消引用指针以创建数组的副本
- C++ 在堆上创建数组而不对其进行初始化
- 数组的维度是在创建数组的那一刻确定的,以后不能更改的,这是真的吗?