将普通数组别名为“std::array”而不是 UB

问题描述 投票:0回答:1

我提出了几种技术来回答使用获取索引的生成器函数初始化 std::array 的惯用语?.

一个经典的回答方法是使用基于

std::index_sequence
的解决方案,但 op 有两个限制:

  • 它应该与非默认可构造类型一起使用
  • 它应该适用于较大的
    std::array
    尺寸,大于常见的
    std::index_sequence
    实现所支持的尺寸。

对大型数组使用

std::array
而不是
std::vector
是有争议的,但这不是“赋值”。

我的一些答案依赖于创建一个临时原始存储,我在其中构建对象,然后将内存别名为指向

std::array
的指针,我用它来移动初始化一个新数组。 我对代码的合法性感到担忧,这可以归结为:

#include <array>
#include <cstddef>

// allocate storage for an std::array and construct its object inplace before
// moving it to destination
template<typename T, std::size_t N, typename Gen) std::array<T,N> make_array(gen)
{
    // checking std::array layout
    static_assert(sizeof(std::array<T, N>) == N * sizeof(T));
    alignas(T) unsigned char storage[N * sizeof(T)];
    for (std::size_t i = 0; i != N; ++i) {
        new (storage + i * sizeof(T)) T(gen(i));
    }
    // aliasing from array of bytes
    // is this legal?
    return std::move(*reinterpret_cast<std::array<T, N>*>(storage));
}

// allocate storage for an std::array inside a vector and construct its object
// inplace before moving it to destination
template<typename T, std::size_t N, typename Gen) std::array<T,N> make_array(gen)
{
    // checking std::array layout
    static_assert(sizeof(std::array<T, N>) == N * sizeof(T));
    auto v = std::vector<T>{};
    v.reserve(N);
    for (std::size_t i = 0; i != N; ++i) {
        v.emplace_back(gen(i))
    };
    // aliasing from array of T
    // is this legal?
    return std::move(
        *reinterpret_cast<std::array<T, N>*>(v.data());
}

游乐场

上面的返回语句有效吗?
如果没有,有办法解决这个问题吗?

请使用标准中的措辞来证明答案的合理性。

c++ language-lawyer undefined-behavior strict-aliasing stdarray
1个回答
0
投票

我认为这是 C++20 后合法的,因为

std::array<T, N>
的实例是隐式创建的。

但是,如果您有

C++20
那么您可以使用
std::bit_cast
,这比
reinterpret_cast
的可疑性要低得多:

// allocate storage for an std::array and construct its object inplace before
// moving it to destination
template<typename T, std::size_t N, typename Gen) std::array<T,N> make_array(Gen gen)
{
    // checking std::array layout
    static_assert(sizeof(std::array<T, N>) == N * sizeof(T));
    alignas(T) unsigned char storage[N * sizeof(T)];
    for (std::size_t i = 0; i != N; ++i) {
        new (storage + i * sizeof(T)) T(gen(i));
    }
    return std::bit_cast<std::array<T, N>>(storage);
}

如果你有 C++23,你可以用

bit_cast
:
做得更好(除非
std::start_lifetime_as

中的副本被省略)
// allocate storage for an std::array and construct its object inplace before
// moving it to destination
template<typename T, std::size_t N, typename Gen) std::array<T,N> make_array(Gen gen)
{
    // checking std::array layout
    static_assert(sizeof(std::array<T, N>) == N * sizeof(T));
    alignas(T) unsigned char storage[N * sizeof(T)];
    for (std::size_t i = 0; i != N; ++i) {
        new (storage + i * sizeof(T)) T(gen(i));
    }
    return *std::start_lifetime_as<std::array<T, N>>(storage);
}
© www.soinside.com 2019 - 2024. All rights reserved.