std :: arrayを使用したstd :: bit_cast


14

彼の最近の講演で、「++現代Cでタイプpunning」ティムールDoumlerは言ったそのstd::bit_castビットを鋳造するために使用することはできませんfloatunsigned char[4] Cスタイルの配列を関数から返すことができないため。のstd::memcpyようなものreinterpret_cast<unsigned char*>(&f)[i]が明確になるときは、C ++ 23(以降)を使用するか待つ必要があります。

C ++ 20では、std::arraywith std::bit_cast

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

Cスタイルの配列の代わりにfloat

回答:


15

はい、これはすべての主要なコンパイラで機能します。標準を見るとわかるように、移植可能であり、動作することが保証されています。

まず第一にstd::array<unsigned char, sizeof(float)>、集合体であることが保証されています(https://eel.is/c++draft/array#overview-2)。このことから、正確にsizeof(float)char内部にsの数を(通常、としてchar[]標準ですが、この特定の実装は必須ではありませんが、要素は隣接している必要があると述べています)、追加の非静的メンバーを持つことはできません。

したがって、簡単にコピーでき、サイズfloatも同じです。

これらの2つのプロパティを使用すると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]; };、文字はその集計の要素である必要がありますが、これにより直接の集計要素のいずれかになりますまたは単一のサブ集計の要素。
Timur Doumler、

2
@ティムールの「まで」は「以上」を意味するものではありません。同じ意味でP -> Qがケースについて何もしないに!P
バリー

1
集合体に要素が4つだけの配列しか含まれていない場合でも、arrayそれ自体にパディングがないことは保証されません。それの実装はパディングを持たないかもしれません(そして、機能不全と見なされるべきであるどんな実装も)、しかしarrayそれ自体がそうしないという保証はありません。
Nicol Bolas、

6

配置とパディングの問題を考慮していないため、受け入れられた答えは正しくありません。

[配列] / 1-3

ヘッダー<array>は、オブジェクトの固定サイズのシーケンスを格納するためのクラステンプレートを定義します。配列は連続したコンテナです。のインスタンスはタイプの要素をarray<T, N>格納するため、それは不変です。NTsize() == N

配列は、N 型をに変換可能な要素まででリスト初期化できる集約ですT

配列は、コンテナおよびリバーシブルコンテナ([container.requirements])のすべての要件を満たしていますが、デフォルトで作成された配列オブジェクトは空ではなく、スワップは複雑ではありません。配列は、シーケンスコンテナーの要件のいくつかを満たしています。ここでは、これらの表のいずれにも記載されていない配列の操作と、追加のセマンティック情報がある操作についてのみ説明します。

この規格では、実際にstd::arrayはタイプのパブリックデータメンバーを1つだけにする必要はない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;
}

float(4バイト)とunsigned charwithの配列なのでsize_of(float)に対するこれらすべてのアサート、根底std::memcpy行われます。したがって、結果の配列の各要素は、floatの1つの連続したバイトになります。

この動作を証明するために、コンパイラエクスプローラで、https//godbolt.org/z/4G21zSで試すことができる小さな例を書きました。フロート5.0はOx40a00000ビッグエンディアンでのフロート数の16進数表現に対応するバイト()の配列として適切に格納されます。


std::arrayパディングビットなどがないことが保証されていますか?
LF

1
残念ながら、一部のコードが機能するという単なる事実は、その中にUBがないことを意味するものではありません。たとえば、auto bits = reinterpret_cast<std::array<unsigned char, sizeof(float)>&>(f)まったく同じ出力を書き込んで取得できます。それは何かを証明しますか?
2019年

仕様による@LF:ContiguiosContainerstd::arrayの要件を満たします(C ++ 17以降)
Manuel Gil

1
@ManuelGil:std::vector同じ基準も満たしており、明らかにここでは使用できません。std::arrayクラス内(フィールド内)の要素を保持して、それが内部配列への単純なポインタにならないようにする必要があるものはありますか?(ベクトルのように、サイズもあり、配列はフィールドにある必要はありません)
firda

@firdaの集約要件ではstd::array、要素を内部に格納する必要がありますが、レイアウトの問題が心配です。
LF
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.