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

phi*_*inz 5 c++ stdarray c++20 std-span

std::array<uint8_t,N>我正在尝试从 a创建 a,std::span<uint8_t,N>但我找不到一种没有memcpy、或 的方法来执行此操作std::copy,或者std::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;
}
Run Code Online (Sandbox Code Playgroud)

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

康桓瑋*_*康桓瑋 6

但我找不到一种没有memcpy,或 的方法来做到这一点std::copy,或者 std::ranges::copy不能保护我免受错误的目标数组大小规范的影响。

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

std::array<uint8_t, span.size()> copy4;
std::ranges::copy(span, copy4.begin());
Run Code Online (Sandbox Code Playgroud)

extent或者您可以通过其静态成员常量(如)获取大小值std::array<uint8_t, span.extent>,这保证可以工作。

  • 不过,使用 `span.extent` 可能会有问题,因为它可能是 `std::dynamic_extent`... (3认同)
  • @phinz `span` 有一个名为 `extent` 的公共静态成员常量,它是模板参数 `Extent` 的值,因此您可以 `decltype(span)::extent` 或只是 `span.extent`。 (2认同)

Jan*_*tke 2

为了扩展@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>{});
    }
}
Run Code Online (Sandbox Code Playgroud)

如果您想进一步减小装配尺寸,可以使用以下条件代替:

std::is_default_constructible_v<T> && std::is_copy_assignable_v<T>
Run Code Online (Sandbox Code Playgroud)

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