从固定大小的 std::span 创建固定大小的 std::array 的惯用方法是什么?

What is the idiomatic way to create a fixed size std::array from a fixed size std::span?

提问人:phinz 提问时间:7/29/2023 更新时间:7/30/2023 访问量:219

问:

我正在尝试从 a 创建一个,但我找不到没有 , 的方法,或者不能保护我免受目标数组大小错误规范的影响。std::array<uint8_t,N>std::span<uint8_t,N>memcpystd::copystd::ranges::copy

#include <algorithm>
#include <array>
#include <iostream>
#include <span>

int main(int argc, char **argv) {
  constexpr size_t N = 10;
  std::array<uint8_t, N> original;
  std::span span(original); // of type std::span<uint8,N>

  std::array copy1(span);                               // does not work
  std::array<uint8_t, N> copy2(span);                   // does not work
  std::array<uint8_t, N> copy3(begin(span), end(span)); // does not work


  // ugly stuff that works, but does not protect me if I specify wrong array size
  constexpr size_t M{N - 1}; //oops, leads to array overflow
  std::array<uint8_t, M> copy4;
  std::copy(begin(span), end(span), copy4.begin());
  std::ranges::copy(span, copy4.begin());

  return 0;
}

在现代 C++ 中执行此操作的惯用方法是什么?

C++ C++20 标准数组 std-span

评论


答:

6赞 康桓瑋 7/29/2023 #1

但是我找不到没有 、 的方法,或者不能保护我免受错误规范的影响 目标数组大小。memcpystd::copystd::ranges::copy

如果 a 具有静态范围,则可以将其实现为常量表达式,该表达式适用于当前主流编译器:spansize()

std::array<uint8_t, span.size()> copy4;
std::ranges::copy(span, copy4.begin());

或者你可以通过其静态成员常量(like )来获取大小值,这是可以保证工作的。extentstd::array<uint8_t, span.extent>

评论

0赞 phinz 7/29/2023
从变量类型中获取范围的方法是什么?就像我可以写的数组一样,我如何从中检索范围?spanstd::tuple_size_v<decltype(original)>decltype(span)
2赞 康桓瑋 7/29/2023
@phinz有一个名为 的公共静态成员常量,它是 template 参数的值,因此您可以或只是 .spanextentExtentdecltype(span)::extentspan.extent
3赞 Jarod42 7/29/2023
不过,使用可能会有问题,因为它可能是......span.extentstd::dynamic_extent
4赞 Jarod42 7/29/2023 #2

你可以把它包装在一个函数中:

template <typename T, std::size_t N>
std::array<T, N> to_array(std::span</*const*/ T, N> s)
requires (N != std::dynamic_extent)
{
    return [&]<std::size_t... Is>(std::index_sequence<Is...>){
        return std::array<T, N>{{s[Is]...}};
    }(std::make_index_sequence<N>());
}

注意:我避免,因为它需要有默认的构造函数(对于初始数组)。std::copyT

评论

2赞 Barry 7/29/2023
可以再来std::array<std::remove_const_t<T>, N>
0赞 phinz 7/29/2023
我选择这个作为答案,因为在一般情况下,不使用默认构造函数确实很重要。
0赞 phinz 7/29/2023
我不喜欢的是,每个元素的初始化都是显式的,因此会生成大量代码。我更喜欢一种在内部利用元素的线性排列并在内部使用循环的方法,在我看来,这个解决方案就像一个展开的循环。但似乎标准库对我的问题缺乏更好的答案。你认为编译器可以再次将展开的循环写成普通循环以避免过多的代码吗?NN
0赞 Jan Schultke 7/29/2023
@phinz您说得对,程序集输出很大,例如对于 godbolt.org/z/cYb9rfGqa。我提供了一个替代解决方案,可以解决简单可复制类型的问题,并且可以涵盖更多情况。
2赞 Jan Schultke 7/29/2023 #3

为了扩展 @Jarod42 的答案,我们可以进行一些改进:

#include <span>
#include <array>
#include <cstring>
#include <algorithm>

// 1. constrain this function to copy-constructible types
template <std::copy_constructible T, std::size_t N>
    requires (N != std::dynamic_extent)
// 2. handle spans of const/volatile T correctly
std::array<std::remove_cv_t<T>, N> to_array(std::span<T, N> s)
// 3. add conditional noexcept specification
    noexcept(std::is_nothrow_copy_constructible_v<T>)
{
    // add type alias so we don't repeat the return type
    using result_type = decltype(to_array(s));
    if constexpr (std::is_trivial_v<T>) {
        // 4. avoid unnecessary instantiations of std::index_sequence etc.
        //    in the cases where we can copy with no overhead (should be fairly common)
        result_type result;
        // note: we cannot simply use std::memcpy here because it would not
        //       correctly handle volatile T
        std::ranges::copy(s, result.begin());
        return result;
    }
    // TODO: consider using std::ranges::copy for all default-constructible
    //       and copyable types, because the following results in huge assembly output
    else {
        // if 4. is not applicable, we still have to use @Jarod42's solution
        return [&]<std::size_t... Is>(std::index_sequence<Is...>) {
            return result_type{s[Is]...};
        }(std::make_index_sequence<N>{});
    }
}

如果要进一步减小程序集大小,可以改用以下条件:

std::is_default_constructible_v<T> && std::is_copy_assignable_v<T>

如果您担心过度初始化会产生开销,则可以使用 ,可能与 一起使用,这应该可以缓解这种情况。std::ranges::copystd::is_trivially_default_constructible_v<T>std::ranges::uninitialized_copy

评论

1赞 Jan Schultke 7/30/2023
@ildjarn你是对的,那么类型需要是微不足道的,而不仅仅是微不足道的可复制的,这样优化才有可能。我已经更新了答案。