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

jgovgodb  于 2023-08-09  发布在  其他
关注(0)|答案(3)|浏览(92)

我试图从std::span<uint8_t,N>创建std::array<uint8_t,N>,但我找不到一种方法来这样做,没有memcpystd::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;
}

字符串
在现代C++中,实现这一点的惯用方法是什么?

rpppsulh

rpppsulh1#

但是如果没有memcpystd::copystd::ranges::copy,我就找不到这样做的方法,因为它们不能防止错误指定目标数组大小。
如果一个span有一个 static 扩展区,它的size()可以实现为一个常量表达式,它可以在当前的主流编译器上工作:

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

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

wfsdck30

wfsdck302#

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

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::copy,因为它需要有T的默认构造函数(用于初始数组)。

xlpyo6sf

xlpyo6sf3#

为了扩展@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::copy在初始化过程中会产生开销,那么可以使用std::is_trivially_default_constructible_v<T>,也可以与std::ranges::uninitialized_copy一起使用,这样可以减轻这种情况。

相关问题