std :: bit_cast与std :: array


14

Timur Doumler 在他最近的演讲“现代C ++中的类型修剪”中,由于不能从函数返回C样式的数组,std::bit_cast因此不能用于将a 强制float转换unsigned char[4]为a。我们应该使用std::memcpy或等到C ++ 23(或更高版本)后,类似的东西reinterpret_cast<unsigned char*>(&f)[i]才能很好地定义。

在C ++ 20中,我们可以在中std::array使用std::bit_cast

float f = /* some value */;
auto bits = std::bit_cast<std::array<unsigned char, sizeof(float)>>(f);

而不是C样式的数组来获取float?的字节?

Answers:


15

是的,这适用于所有主要的编译器,据我观察标准可以确定,它是可移植的并且可以保证正常工作。

首先,std::array<unsigned char, sizeof(float)>保证是一个聚合(https://eel.is/c++draft/array#overview-2)。由此可知,它内部恰好包含sizeof(float)多个chars(通常为char[]由此可见,尽管从原则上讲,该标准并未强制要求该特定实现-但它确实说元素必须是连续的),并且不能具有任何其他非静态成员。

因此,它是可复制的,并且其大小与 float相同。

这两个属性允许您bit_cast在它们之间。


3
请注意,它struct X { unsigned char elems[5]; };满足您所引用的规则。当然,可以使用最多4个元素对列表进行初始化。它可以可以列表初始化5个元素。我认为没有任何标准库实现者会讨厌这样做的人,但是我认为这在技术上是一致的。
巴里

谢谢!–巴里,我认为那是不对的。该标准说:“最多可以用N个元素进行列表初始化”。我的解释是“最多”意味着“不超过”。这意味着你做不到elems[5]。在那一点上,我看不到如何最终得到汇总sizeof(array<char, sizeof(T)>) != sizeof(T)
Timur Doumler,

我认为该规则(“可以通过列表初始化的聚合...”)的目的是允许struct X { unsigned char c1, c2, c3, c4; };struct X { unsigned char elems[4]; };–因此,尽管char必须是该聚合的元素,但这允许它们成为直接聚合的元素或单个子集合的元素。
Timur Doumler,

2
@Timur“最多”并不表示“不超过”。同样,这种暗示P -> Q并不暗示任何情况!P
-Barry

1
即使合计仅包含4个元素组成的数组,也无法保证array自身不会填充。它的实现可能没有填充(并且任何实现都应该被认为是功能失调的),但是不能保证它array本身不会。
Nicol Bolas

6

接受的答案是错误的,因为它没有考虑对齐和填充问题。

[array] / 1-3

标头<array>定义用于存储固定大小的对象序列的类模板。数组是一个连续的容器。array<T, N>存储N类型为的元素的实例T,因此size() == N是不变的。

数组是一个聚合,最多可以将N 其类型转换为的元素进行列表初始化T

数组满足容器和可逆容器([container.requirements])的所有要求,除了默认构造的数组对象不为空并且交换没有恒定的复杂性。数组满足序列容器的某些要求。在此仅提供对这些表之一中未描述的数组操作以及存在附加语义信息的操作的描述。

该标准实际上不需要std::array完全具有一个类型的公共数据成员T[N],因此从理论上讲,sizeof(To) != sizeof(From)或是可能的is_­trivially_­copyable_­v<To>

但是,如果这在实践中不起作用,我将感到惊讶。


2

是。

根据描述的行为的论文std::bit_cast及其所建议的实现,只要这两种类型具有相同的大小并且可以轻松复制,则强制转换应该成功。

的简化实现std::bit_cast应类似于:

template <class Dest, class Source>
inline Dest bit_cast(Source const &source) {
    static_assert(sizeof(Dest) == sizeof(Source));
    static_assert(std::is_trivially_copyable<Dest>::value);
    static_assert(std::is_trivially_copyable<Source>::value);

    Dest dest;
    std::memcpy(&dest, &source, sizeof(dest));
    return dest;
}

由于浮子(4个字节)和阵列unsigned charsize_of(float)对于所有这些断言,底层的std::memcpy将被进行。因此,结果数组中的每个元素将是浮点数的一个连续字节。

为了证明这种行为,我在Compiler Explorer中写了一个小例子,您可以在这里尝试:https : //godbolt.org/z/4G21zS。浮点数5.0正确地存储为字节数组(Ox40a00000),该字节数组对应于Big Endian中该浮点数的十六进制表示形式。


您确定std::array可以确保没有填充位等吗?
LF

1
不幸的是,仅仅有些代码有效的事实并不意味着其中没有UB。例如,我们可以编写auto bits = reinterpret_cast<std::array<unsigned char, sizeof(float)>&>(f)并获得完全相同的输出。有什么证明吗?
EVG

根据说明书@LF:std::array满足的要求ContiguiosContainer (因为C ++ 17)
曼努埃尔·吉尔

1
@ManuelGil:std::vector也满足相同的条件,显然不能在此处使用。有什么要求吗std::array将元素保存在类中(在字段中),以防止其成为指向内部数组的简单指针?(例如向量,也具有大小,数组不需要在字段中包含)
firda

@firda的总要求 std::array有效地要求将其存储在内部,但我担心布局问题。
LF
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.