(剧透-这是一个自回答的问题)
假设我有两个索引序列,例如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,但不适用于某些方法。更复杂的类型,这些类型不是默认可构造的(显然,它们不是索引序列的一部分,但可以是其他类型)。
有没有不需要循环和默认构造值的解决方案?任何可用的C ++标准都是公平的游戏。
答案 0 :(得分: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...));
}
答案 1 :(得分: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{});
答案 2 :(得分: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;
}
答案 3 :(得分: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;
}
}
答案 4 :(得分: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::merge
是constexpr
,则函数join_arr
是5线。
如果您不知道i1
和i2
已排序,则需要实现自己的constexpr
的{{1}}版本。
如果要交替组合索引序列,可以类似地使用
std::sort
而不是(b)。只要您不实际抛出, 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];
}
语句就没问题。 (因此,异常被转换为编译时错误。这正是您想要的。)
最后,如果类型不是默认构造,则可以编写
throw
而不是(a),即,您用一些随机实例填充结果。这仅需要可复制的副本(所有现有解决方案都需要)。