GCC9はstd :: variantの値のない状態を回避できますか?


14

私は最近、std::visitコンパイラ間の最適化の素晴らしい比較につながるRedditディスカッションをフォローしました。私は次のことに気づきました:https : //godbolt.org/z/D2Q5ED

GCC9とClang9の両方(同じstdlibを共有していると思います)は、すべての型がいくつかの条件を満たす場合に、値のない例外をチェックしてスローするためのコードを生成しません。これはより優れたcodegenにつながるため、MSVC STLで問題を提起し、次のコードが提示されました。

template <class T>
struct valueless_hack {
  struct tag {};
  operator T() const { throw tag{}; }
};

template<class First, class... Rest>
void make_valueless(std::variant<First, Rest...>& v) {
  try { v.emplace<0>(valueless_hack<First>()); }
  catch(typename valueless_hack<First>::tag const&) {}
}

主張によると、これによりすべてのバリアントが無価値になり、ドキュメントを読むと次のようになります。

まず、現在含まれている値(存在する場合)を破棄します。次にT_I、引数を使用してtypeの値を作成する場合と同様に、含まれている値を直接初期化します。std::forward<Args>(args)....例外がスローされた場合、*thisvalueless_by_exceptionになることがあります。

わからないこと:なぜ「かもしれない」と記載されているのですか?操作全体がスローされた場合、古い状態を維持することは合法ですか?これはGCCが行うことなので、

  // For suitably-small, trivially copyable types we can create temporaries
  // on the stack and then memcpy them into place.
  template<typename _Tp>
    struct _Never_valueless_alt
    : __and_<bool_constant<sizeof(_Tp) <= 256>, is_trivially_copyable<_Tp>>
    { };

そして後でそれは(条件付きで)次のようなことをします:

T tmp  = forward(args...);
reset();
construct(tmp);
// Or
variant tmp(inplace_index<I>, forward(args...));
*this = move(tmp);

したがって、基本的には一時ファイルを作成し、それが成功すると、それを実際の場所にコピー/移動します。

IMOこれは、ドキュメントで述べられている「最初に、現在含まれている値を破棄する」の違反です。私が標準を読んだときv.emplace(...)、バリアントの現在の値は常に破棄され、新しい型はセット型または値なしのいずれかになります。

この条件is_trivially_copyableでは、観測可能なデストラクタを持つすべての型が除外されています。したがって、これは、「as-ifバリアントが古い値で再初期化される」などと考えられます。しかし、バリアントの状態は観察可能な影響です。それで標準は実際に許可しemplaceますか、それは現在の値を変更しませんか?

標準的な見積もりに応じて編集します。

次に、含まれている値を、引数でTI型の値を直接非リスト初期化するかのように初期化しますstd​::​forward<Args>(args)...

T tmp {std​::​forward<Args>(args)...}; this->value = std::move(tmp);本当に上記の有効な実装としてカウント?これは「まるで」の意味ですか?

回答:


7

標準の重要な部分はこれだと思います:

https://timsong-cpp.github.io/cppwp/n4659/variant.mod#12から

23.7.3.4修飾子

(...)

テンプレートvariant_alternative_t>&emplace(Args && ... args);

(...)含まれている値の初期化中に例外がスローされた場合、バリアントは値を保持しない可能性があります

それは「する必要がある」ではなく「する必要がある」と述べています これは、gccで使用されるような実装を可能にするために意図的なものであると期待しています。

あなた自身が言ったように、これはすべての選択肢のデストラクタが取るに足らないものであり、以前の値を破壊する必要があるため観察できない場合にのみ可能です。

フォローアップの質問:

Then initializes the contained value as if direct-non-list-initializing a value of type TI with the arguments std​::​forward<Args>(args)....

T tmp {std :: forward(args)...}; this-> value = std :: move(tmp); 上記の有効な実装として本当にカウントされますか?これは「まるで」の意味ですか?

はい、簡単にコピーできる型の場合、違いを検出する方法がないため、実装は、値が説明どおりに初期化されたかのように動作します。タイプが簡単にコピーできない場合、これは機能しません。


面白い。フォローアップ/明確化のリクエストで質問を更新しました。ルートは:コピー/移動は許可されていますか?might/may標準が代替案が何であるかを述べていないので、私はこの文言に非常に混乱しています。
Flamefire、

これを標準の見積もりで受け入れますthere is no way to detect the difference
Flamefire、

5

それで標準は実際に許可しemplaceますか、それは現在の値を変更しませんか?

はい。 emplace漏れのないことの基本的な保証を提供するものとします(つまり、構築と破壊が観察可能な副作用を生成するときのオブジェクトの寿命を尊重します)。

variantユニオンと同様に動作する必要があります—代替は適切に割り当てられたストレージの1つの領域に割り当てられます。動的メモリを割り当てることはできません。したがって、型を変更しemplaceても、追加の移動コンストラクターを呼び出さずに元のオブジェクトを保持する方法はありません。オブジェクトを破棄して、代わりに新しいオブジェクトを構築する必要があります。この構築が失敗した場合、バリアントは非常に価値のない状態に移行する必要があります。これは、存在しないオブジェクトを破壊するような奇妙なことを防ぎます。

ただし、小さな自明なコピー可能な型の場合、オーバーヘッドをあまりかけずに強力な保証を提供することができます(この場合、チェックを回避するためのパフォーマンスが向上します)。したがって、実装はそれを行います。これは標準に準拠しています。実装は、標準により要求される基本的な保証を、よりユーザーフレンドリーな方法で提供します。

標準的な見積もりに応じて編集します。

次に、含まれている値を、引数でTI型の値を直接非リスト初期化するかのように初期化します std​::​forward<Args>(args)...

T tmp {std​::​forward<Args>(args)...}; this->value = std::move(tmp);本当に上記の有効な実装としてカウント?これは「まるで」の意味ですか?

はい、移動割り当てが観察可能な効果をもたらさない場合、それは自明にコピー可能なタイプの場合です。


私は論理的推論に完全に同意します。これが実際に標準にあるかどうかはわかりませんか?これを何かでバックアップできますか?
Flamefire、

@Flamefire Hmm ...一般に、標準機能は基本的な保証を提供し(ユーザーが提供するものに何か問題がない限り)、それstd::variantを破る理由はありません。これは標準の文言でより明示的にできることに同意しますが、これは基本的に標準ライブラリの他の部分が機能する方法です。そして参考までに、P0088が最初の提案でした。
LF

ありがとう。内部にはより明確な仕様があります。if an exception is thrown during the call toT’s constructor, valid()will be false;そのため、この「最適化」は禁止されました
Flamefire

はい。emplaceP0088以下の仕様Exception safety
Flamefire、

@Flamefireは、元の提案と投票したバージョンとの間に矛盾があるようです。最終バージョンは「5月」の表現に変更されました。
LF
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.