質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

Q&A

解決済

2回答

1271閲覧

c++20のstd::bit_castに相当するコード

tyu_ru_cpp

総合スコア40

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

0グッド

2クリップ

投稿2019/04/11 14:38

前提・実現したいこと

c++20で標準ライブラリに実装されるstd::bit_castについて

std bit_cast - cpprefjp
std::bit_cast - cppreference.com

の説明を読んでいたところ、cpprefjpでは

そのような目的にはstd::aligned_storageとstd::memcpy()を組み合わせて使用することになるが、

となっていますが、cppreferenceにはc++17以前のコードとして

c++

1template <class To, class From> 2typename std::enable_if< 3 (sizeof(To) == sizeof(From)) && 4 std::is_trivially_copyable<From>::value && 5 std::is_trivial<To>::value, 6 // この実装は To がトリビアルにデフォルト構築可能であることを要求します。 7 To>::type 8// constexpr のサポートはコンパイラマジックが必要です。 9bit_cast(const From &src) noexcept 10{ 11 To dst; 12 std::memcpy(&dst, &src, sizeof(To)); 13 return dst; 14}

が掲載されていました。こちらのコードにはstd::aligned_storageが使われていないようです。

教えていただきたいこと

  1. std::aligned_storageを用いる方法とそうでない方法で、どのような違いがあるか
  2. c++17以前でstd::bit_castのようなtype punningを行うとき、変換前後の型が整数型、浮動小数点型に限られる場合、constexprにこれを行う方法があるか

よろしくおねがいします

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

ベストアンサー

std::aligned_storageを用いる方法とそうでない方法で、どのような違いがあるか

変換先型Toに対する「デフォルトコンストラクタの要求」有無が異なると思います。より汎用性の高い実装では、デフォルトコンストラクタを必要としない std::aligned_storage が必要となります。

cpprefjpの文言は、提案文書 P0476R2 からの引用・翻訳と思われます。原文後半にはaligned_storageが必要となる理由への言及があります(太字)。

Attuned developers use aligned_storage with memcpy, avoiding alignment pitfalls and allowing them to bit-cast non-default-constructible types.

提案者自身による実装例 jfbastien/bit_cast も、下記実装になっています(引用に際して一部簡略化しました):

C++

1template<typename To, typename From> 2inline constexpr To bit_cast(const From& from) noexcept { 3 typename std::aligned_storage<sizeof(To), alignof(To)>::type storage; 4 std::memcpy(&storage, &from, sizeof(To)); 5 return reinterpret_cast<To&>(storage); 6 // More common implementation: 7 // std::remove_const_t<To> to{}; 8 // std::memcpy(&to, &from, sizeof(To)); 9 // return to; 10}

c++17以前でstd::bit_castのようなtype punningを行うとき、変換前後の型が整数型、浮動小数点型に限られる場合、constexprにこれを行う方法があるか

C++17標準仕様の範囲内では不可能と思われます。だからこそ std::bit_cast 関数が追加されたはずです。C++20でも結局はコンパイラによる特殊サポートが必須となります。(std::bit_castは"コンパイラマジック”関数として実現されます)

提案文書 P0476R2 §1. Backgroundより引用:

Furthermore, it is currently impossible to implement a constexpr bit-cast function, as memcpy itself isn't constexpr. Marking the proposed function as constexpr doesn't require or prevent memcpy from becoming constexpr, but requires compiler support. This leaves implementations free to use their own internal solution (e.g. LLVM has a bitcast opcode).

投稿2019/04/12 06:59

編集2019/04/12 07:03
yohhoy

総合スコア6191

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

tyu_ru_cpp

2019/04/12 12:44

ありがとうございました。解りやすかったです。
guest

0

こんにちは。

ごめんなさい。回答にはなりませんでした。

1.std::aligned_storageを用いる方法とそうでない方法で、どのような違いがあるか

cppreference のようなコードの場合、TO の領域を確保した時に TO のコンストラクタが走ると思います。
その上で、データを上書きするのでかなり危険な感じがします。

TO は std::is_trivially_copyable が true ですが、trivially_copyableな型でもコンストラクタを持てないわけではなさそうです。
しかし、上記ページにはis_trivially_copyableであれば std::memcpy 可能とも書かれていますね。例えば、副作用があるようなデフォルト・コンストラクタを持っている型へ std::memcpy ってリスキーな印象を受けます。
更にしかし、規格上それがプログラマの責任とされているのならば、cppreference のコードも規格内ということになりそうです。

そして、そのようなコンストラクタの発動を恐れる場合は、アライメントが取れているメモリ領域を確保してそこへ std:memcpy すればよいと思いますので、このような実装をする場合には、std::aligned_storage を使うとスマートにかけそうです。
この辺は、ライブラリの実装者の考え方に依存しそうです。(なんとなくgccは前者、msvcは後者をとりそうな。偏見かも?)

2.c++17以前でstd::bit_castのようなtype punningを行うとき、変換前後の型が整数型、浮動小数点型に限られる場合、constexprにこれを行う方法があるか

ここA Proposal to Add Constexpr Modifiers to Functions in <algorithm> and <cstring> Headers を見るとこの提案がC++17に採用されていなかった場合、compiler intrinsics を使わないと無理そうですね。

投稿2019/04/12 03:41

編集2019/04/12 03:43
Chironian

総合スコア23272

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

yumetodo

2019/04/12 03:50

trivialに関しては前にQiitaに記事を書いていて https://qiita.com/yumetodo/items/424cc4d15de4edad436a 実はC++11/14と17でちょっと定義が違います。 ただ、いずれにしても、trivially copyable classならばmemcpyでのコピーが可能です。単なるバイトコピーではできない場合はそもそもユーザー定義のcopy/move ctor/assign opを提供しなければなりません(さもないとそもそも動かん)。 alignがなぜ必要なのかはちょっと私もわからないですが・・・。
Chironian

2019/04/12 04:12

yumetodoさん ちょっと教えて下さい。定義を見る限り、trivially copyable classなクラスは、C++11/C++14共にコンストラクタ(例えばデフォルト・コンストラクタ)を持てそうですが、実際どうなのでしょう?(trivialでゲシュタルト崩壊してしまったので 私の読み損ないかも知れず、自信がないのです。) もし、持てるのであれば、そのようなクラスへ std::memcpy することが安全とは思えなくて。
yumetodo

2019/04/12 04:35

copy/move**ではない**ctorは持つことができます。trivially copyable classならばmemcpyでのコピーが可能です。 なぜか?それは単純なbyte copyで壊れるクラスには現実的にcopy/move ctorがユーザー定義されていなければ実用に耐えず、またtrivially copyable classという概念もそれを前提においています。
yumetodo

2019/04/12 05:37

https://twitter.com/onihusube9/status/1116557840175341569?s=20 >C++17までのmemcpyを用いた合法的type punningにaligned_storageが必要なのは、可搬性のため(多分 >アラインされていない領域へのアクセスはCPUによっては例外を投げうる >x86だけしか見ないならいらないのかも
Chironian

2019/04/12 05:38

あ、いえ、その意味ではなくて、例えば、副作用のあるデフォルト・コンストラクタを持つクラスのインスタンスへ std::memcpyするのは、必ずしも安全ではないように思うのですよ。でも、安全とあるので不思議に感じてます。
yumetodo

2019/04/12 05:51

なんで安全ではないのかがわからない。どういう懸念だろうか?
Chironian

2019/04/12 07:15

なんで「安全」なのか分からないのです。 「コンストラクタで初期化する」とプログラムしているのにそれをすっとばさせるって危なくないですか?
yohhoy

2019/04/12 07:30 編集

(横からごめんなさい) "trivially copyable"は「ビット単位コピーでオブジェクトを複製できること」しか気にしませんから、(非copy/非moveな)コンストラクタが何をしようが定義上無関係ということになっています。 Chironianさんの懸念する安全性(プログラム動作としての一貫性と表現すべきもの?)は、trivially copyableという性質だけを見た場合、良し悪しはさておきスコープ外という解釈になるかと思います。 仮にデフォルトコンストラクタが何か特別な処理を行う場合でも、「構築後のオブジェクト状態しか議論の対象とならない」という表現でも良いかもしれません。
Chironian

2019/04/12 07:55

yohhoyさん ありがとうございます。なるほど。なんとなくイメージ掴めました。 ちゃんとtrivialを理解できていないのにmemcpyするのはリスクがあるということのようですね。 通常はmemcpyするべきでないというのは「C++の常識」なので問題なしということなのかも。 ということは私のようにtrivialをよく分かってない人は、std::bit_cast を使うのはコンストラクタをユーザ定義していない型(組み込み型やenum型含む)に限定した方が良さげですね。ほとんどの場合、それで十分でしょうし。
yumetodo

2019/04/12 08:26 編集

いやいや、待ってください。本当にChironianさんがなにを懸念しているのか全くわからない。 例えばstd::stringのようなクラスではメンバに動的確保された領域へのポインタをもちますよね? しかしこの場合メモリー開放のために普通デストラクタや正しいcopyのためにcopy ctorを実装したりすると思いますが、その場合 デストラクタと全てのコピー/ムーブ コンストラクタ/代入演算子はuser-providedではない が条件となるtrivially copyable classではなくなりますよね?つまりRAIIなクラスはtrivially copyable classになりません。 またuser-providedではないということは= default/= delete 指定ができないということです。ここでctor/assign opが自動生成される条件を思い出していただきたいのですが https://yohhoy.hatenadiary.jp/entry/20140704/p1 copy/move ctor**以外の**任意のコンストラクタ(デフォルトコンストラクタ含む)をユーザー定義した場合、copy/move ctor/assign opは自動生成されるのでした。 さて、ということはcopy/move ctor**以外の**任意のコンストラクタ(デフォルトコンストラクタ含む)でなんらかの副作用があるにもかかわらず、copy/move ctor/assign opをユーザー定義しない場合、 memcpy以前に T a; T b = a; だけで問題が起こることになります。そんなクラス設計をChironianさんはこれまで見たことがありますか?もし見たとしたらそのコードごとゴミ箱に投げ捨てて、書いた人にfワードを叩きつけ、お祓いに行くべきです。
Chironian

2019/04/12 09:15

yumetodoさん copyやmoveをユーザ定義していないものは、std::memcpyでコピーしても安全な筈ということでしょうか?(その他にもいくつか条件が合ってそれらも満たせばOKということですね。) なんとなく成り立ちそうな気もしますが、そうでもないケースはありそうです。 デフォルト・コンストラクタのみユーザ定義し、その中で新規生成した旨をログ出力しているクラスが有ったとします。その他のtrivially_copyable の条件も満たしているものとします。 このクラスのインスタンスに対するstd::bit_cast処理は、cppreference.com のサンプル・コードのような実装とyohhoyさんが書かれている原提案者のような実装では振る舞いが異なります。前者の実装ではデバッグで地獄を見そうな気がします。(bit_castする度にインスタンスが新規生成されるけど、その後一切動かないように見える筈) そして、その地獄の責任は「cppreference.com のサンプル・コードのような実装をしたプログラマ」に帰属するような気がします。このケースではコピー・コンストラクタが行わないコンストラクト処理を実行してはいけないと思います。 ここは安全性より性能(もしくは自由度)を優先した結果、中途半端な理解が不具合を潜在化させたということと理解しました。(C++ではよくあることかも)
yumetodo

2019/04/12 10:13

それは意図しないタイミングでctorを呼ばれるのがまずい、という話であって、コピーして大丈夫かとは話が異なるのではないかなと
Chironian

2019/04/12 10:49 編集

yumetodoさん あああ、なるほど! trivially_copyable なクラスは、コピーコンストラクタやコピー代入演算子の代わりに std::memcpy を使ってコピーした時、コピーコンストラクタやコピー代入演算子で得られる結果と同じ結果が得られる筈ということですね。 コピー以外の局面については関知しないということか。確かに最初からそう言ってますね。やっと追いつけたようです。申し訳ない。 cppreference.com のサンプル・コードはデフォルト・コンストラクタを呼び出すという副作用があるので好ましい実装ではなく、副作用のない実装をするには コンストラクタを起動しない方法でメモリ獲得するべきだけど配列等で単にメモリを確保するだけではアライメントが取れていない可能性があるから、aligned_storage を使えばOKということですね。 ありがとうです!!
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問