問題タブ [expression-templates]
For questions regarding programming in ECMAScript (JavaScript/JS) and its various dialects/implementations (excluding ActionScript). Note JavaScript is NOT the same as Java! Please include all relevant tags on your question; e.g., [node.js], [jquery], [json], [reactjs], [angular], [ember.js], [vue.js], [typescript], [svelte], etc.
c++ - 式テンプレートの使用時に左辺値/右辺値に応じてクラスの異なる実装を提供する
問題
string
文字列を表すクラスを実装するとします。operator+
次に、 2 つの を連結する を追加し、を実行するときに複数の割り当てを回避するために式テンプレートstring
を介して実装することにします。str1 + str2 + ... + strN
オペレーターは次のようになります。
stringbuilder
はテンプレート クラスであり、オーバーロードoperator+
し、暗黙的string
な変換演算子を持ちます。ほとんど標準的な教科書の演習:
上記の実装は、誰かがそうする限り完全に機能します
ただし、微妙なバグがあります。結果を正しい型の変数に代入すると、その変数は式を構成するすべての文字列への参照を保持します。これは、たとえば、文字列の 1 つを変更すると結果が変わることを意味します。
式テンプレート内に str1 参照が格納されているため、これによりfiebar が出力されます。ひどくなる:
これで、式テンプレートに破棄された値への参照が含まれ、プログラムがすぐにクラッシュします。
今それは何right_type
ですか?もちろんstringbuilder<stringbuilder<...>, string>
、それは式テンプレートマジックが生成する型です。
では、なぜそのような隠し型を使用するのでしょうか? 実際、明示的に使用することはありませんが、C++11 の auto は使用します!
質問
要するに、式テンプレートを実装するこの方法 (値をコピーしたり、共有ポインターを使用したりする代わりに安価な参照を保存することによって) は、式テンプレート自体を保存しようとするとすぐに壊れてしまうようです。
したがって、右辺値または左辺値を構築しているかどうかを検出し、右辺値が構築されている (参照を保持する) か、左辺値が構築されている (コピーを作成する) かによって、式テンプレートの異なる実装を提供する方法がかなり必要です。 )。
この状況を処理するための確立された設計パターンはありますか?
調査中にわかった唯一のことは、
this
左辺値または右辺値に応じて、メンバー関数をオーバーロードできます。ただし、コンストラクターでもそれを行うことはできないようです。
C++ では、実際には「型オーバーロード」を行うことはできません。つまり、型の使用方法(左辺値または右辺値として作成されたインスタンス) に応じて、同じ型の複数の実装を提供することはできません。
c++ - データ メンバーと右辺値の有効期間
Paul Preney によって作成されたExpression templates および C++11の式テンプレート コードに何らかの形で触発されて、以下をテストすることにしました。
次に、それを使用して、次のようにX<const vector<int>&>
andのインスタンスを生成しました。X<vector<int>&&>
出力は次のとおりです。
これは、一時的な有効期間vector<int>{5,6,7,8}
が延長されておらず、右辺値参照メンバーが別のX::t
ものにバインドされていることを示しています。
さて、この回答から右辺値への参照をconstするクラスデータメンバーの寿命は何ですか? 、これが予想される動作であることはわかっています。
ただし、ここでの質問は、右辺値参照メンバーが存在する限り一時ベクトルの存在を許可するExpression テンプレートと C++11の Paul Preney のコードの違いは何ですか? 一時的に作成されるケース 2 を参照してください。
どうやら、同じ構造がここで使用されているようですが、おそらく何かが欠けています。
編集: 以下の R. Martinho Fernandes の回答に基づいて、次のことを試しました。
そして、これが有効なコードであることがわかります。
したがって、式テンプレートに格納されている参照は明らかにダングリングではありません。ここで何が起こっているのですか?
c++ - 式テンプレート ツリーの変換
式テンプレート ツリーが与えられた場合、処理する前に最適化された新しいツリーを作成したいと考えています。次の乗算演算の例を考えてみましょう。
の左から右への結合性によりoperator*
、次の式ツリーが生成されます。
乗算が右から左に行われる変換された式ツリーを作成したいと思います。
二項式の型を考えてみましょう:
乗算演算子 を定義しますoperator*
。
は次のようConstify
に定義されます。
左辺値から構築された場合は const 左辺値参照を使用し、右辺値から構築された場合は右辺値のコピー (コピー/移動による) を含む部分式を確保するために使用されます。
前の条件で新しい式テンプレート ツリーを作成する変換関数を定義します。
私の質問は次のとおりです。
1) 関数の戻り値の型を決定するにはどうすればよいTransform
ですか? 次のような型特性を使用してみました。
以下の私の質問(2)を解決するためにこれを適用する方法がわかりません。
2) 再帰行の書き方:
3)この問題のよりクリーンな解決策はありますか?
編集: DyP は、上記の問題に対する部分的な解決策を提示します。以下は、彼の答えに基づく私の完全な解決策です。
出力:
Transform
最も外部の呼び出し以外のすべてのサブ式に関数を適用する必要があることに注意してTransform
ください (最後のTransform
オーバーロードを参照)。
完全なソース コードはここにあります。
c++ - 式テンプレート + CRTP + AMP == カーネル生成
私は最近、表現テンプレートの素晴らしさを発見し、その使用法についてある程度満足できるレベルの理解とスキルに達しましたが、イディオムを新たに使用したいと考えています。どうやってこの問題にたどり着いたかについての長い話は飛ばしますが、この問題は価値の点で証明されています。
wikiにあるものと同様の基本的な式クラスを作成しようとしていますが、C++AMP と互換性のある形式です。つまり、操作はすべて C++AMP カーネルで行われます。個々の基本操作を個別のカーネルとして持つような大規模なベクトル操作にラッピング クラスを簡単に作成できますが、これは非常に非効率的です。最終的に操作を単一のカーネルにマージするラッピング式テンプレート クラスを作成しようとしています。
wiki のサンプル コードを考えると、これは、Vec クラスのコピー コンストラクター内で次のように記述することを意味します。
通常の for ループの代わりに。これに関する唯一の問題は、restrict(amp) 関数内では、C++AMP 仕様のセクション 2 、最も重要なセクション 2.4 で説明されている制限を持つ amp 互換クラスのみを使用できることです。最大の制限は、C++AMP 互換クラスが、concurrency::array 以外の参照メンバーを持つことができないことです。これは Expression Tempalte のイディオム (ここでは間違った言葉を使用している可能性があります) を完全に破壊します。そこでは操作が互いにパックされ、すべて内部オペランドへの参照が保持されます。コンパイラは(const)参照以外のメンバーを持たないクラスのみを「シースルー」するため、AFAIKの値による格納もオプションではありません。
これを機能させる方法、または完全にホスト側の C++ であり、後ですべて C++AMP 互換の構造にキャストする代替ルートを見つける方法はありますか? 最終的には、GPGPU の知識がなくても、コード生成ツールを作成しなくても効率的に使用できるラッピング クラスを作成できるようになりたいと考えています。
前もって感謝します。
ps .: 当然、index_type は concurrency::index<1> であり、container_type は concurrency::array または concurrency::array_view のいずれかで、問題の解決に役立ちます。array_view は論理的にきれいです。つまり、Vec クラスはクラス外の配列を使用して作成され、Vec は array_views のみをその配列に格納しますが、array_views はどのような形式でも参照メンバーとして許可されません。さらに、論理的に配列は、実際には同じ物理配列を指している可能性のある異なるarray_viewですべての操作を実行するのではなく、コンパイラー。
c++ - コンパイラが式テンプレートでピープホール最適化を行うのを妨げているのは何ですか?
私は以下にリストされたコードを持っています:
ここでva1
、 とva2
は 2 つのvalarray<int>
オブジェクトで、はとk
のサイズです。私が期待しているのは、コンパイラが次のように行を最適化することです。va1
va2
printf
しかし代わりに、Intel コンパイラ (13.1) と CLang (3.4) の両方がそのような最適化を行いませんでした。たとえば、Intel コンパイラは次のアセンブリ コードを出力しました。
ここでr13
、 の値を格納しk
、r14
およびはそれぞれおよびr12
のメモリの先頭です。はイテレータ変数です。コードから、それが行うことは次のとおりです。va1
va2
r15
i
(-O3 を使用しても) 最適化されない理由
のぞき穴の最適化で?この場合、Gcc 4.8.2 は最適化を行いますが、処理できません-(va1[i]+va2[i])+(va1[i]-va2[i])
。
考えられる理由の 1 つは、前述のコードで式テンプレートが使用されていることです。問題は、なぜコンパイラは最適化を 1 段階手前で停止したのかということです。式テンプレートはどのようにして前進を妨げましたか?
注 ええと、答えは常に「コンパイラがその最適化を行うように設計されていないため」です。しかし、私がドラゴンブックから学んだ限りでは、コンパイラは、何も改善できなくなるまで最適化を繰り返し行う必要があります。
c++ - Expression Template の実装が最適化されていない
私は C++ の式テンプレートの概念を理解しようとしています。そのため、サンプル コードなどを組み合わせて単純なベクトルと関連する式テンプレート インフラストラクチャを生成し、バイナリ演算子 (+、-、) のみをサポートしています。
すべてがコンパイルされますが、標準の手書きループと式テンプレートのバリアントのパフォーマンスの違いが非常に大きいことに気付きました。ET は手書きの 2 倍近く遅いです。違いを期待していましたが、それほどではありませんでした。
完全なコード リストは次の場所にあります。
https://gist.github.com/BernieWt/769a4a3ceb90bb0cae9e
(乱雑なコードで申し訳ありません。)
.
要するに、私は基本的に次の 2 つのループを比較しています。
ET:
ハードウェア:
出力を逆アセンブルすると、次のようになります。違いは明らかに、追加の memcpy と、ET バリアントのリターン中に発生するいくつかの 64 ビット ロードです。
私の質問は次のとおりです。この時点で、コピーを削除する方法に行き詰まっています。基本的に、コピーなしで v4 をその場で更新したいと考えています。これを行う方法についてのアイデアはありますか?
注 1: GCC 4.7/9、Clang 3.3、VS2010/2013 を試しました。言及されているすべてのコンパイラでほぼ同じパフォーマンス プロファイルが得られます。
注2: vecのbin_expを前方宣言してから、次の代入演算子を追加し、bin_expから変換演算子を削除しようとしましたが、役に立ちませんでした:
更新注 2 に示されている解決策は、実際には正しいものです。コンパイラは、手書きのループとほぼ同じコードを生成します。
.
別のメモとして、ET バリアントのユースケースを次のように書き直すと、次のようになります。
クラッシュは、ET のインスタンス化中に生成される一時 (右辺値) が割り当て前に破棄されるために発生します。C++11 を使用してコンパイラ エラーを引き起こす方法があるかどうか疑問に思っていました。
c++ - Eigen::CwiseBinaryOp から MatrixXd へのキャストで segfault が発生する
必要な複雑な計算を行うために、メンバー変数として Eigen 式テンプレートを格納するライブラリを作成しています。ただし、MatrixXd などで直接変換しない限り、これらの式テンプレートを保存または返すことができないようです。これにより、すべてのステップが一時的に保存され、設計全体の効率が損なわれます。
問題の原因となる短い例を次に示します。ホルダーは固有行列を保持するだけで、Summer は 2 つのホルダーを取得し、get() を呼び出すと、それらが保持する 2 つの行列の合計を出力します。次のテストは、合計式テンプレートが行列に評価されると失敗します (segfault または std::bad_alloc)。
インクルードファイル
簡単なテスト
- インクルード ファイルでは、代わりにコメント アウトされた typedef を使用すると、正常に動作します。
- 問題はぶら下がっている参照によるものだと思われますが、それを証明することはできません。
c++11 - ネストされた std::forward_as_tuple とセグメンテーション違反
私の実際の問題はもっと複雑で、ここでそれを再現するための短い具体的な例を示すことは非常に難しいようです. したがって、関連する可能性のある別の小さな例をここに投稿しています。その議論は、実際の問題にも役立つ場合があります。
実際の問題には が含まれていないstd::tuple
ため、例を独立させるために、カスタムの最小限の大まかな同等物を次に示します。
これらの定義を考えると、まったく同じ動作になります。
一般に、動作はコンパイラと最適化レベルに依存するようです。デバッグしても何もわかりませんでした。すべての場合において、すべてがインライン化および最適化されているように見えるため、問題の原因となっている特定のコード行を特定できません。
それらへの参照がある限り一時変数が存続することになっている場合 (関数本体内からローカル変数への参照を返さない場合)、上記のコードが問題を引き起こす可能性がある根本的な理由と、ケース A とB は異なるはずです。
私の実際の問題では、Clang と GCC の両方で、最適化レベルに関係なく、ワンライナー バージョン (ケース A) でもセグメンテーション エラーが発生するため、問題は非常に深刻です。
代わりに値または右辺値参照 (たとえばstd::make_tuple
、またはnode <A...>
カスタム バージョン) を使用すると、問題はなくなります。タプルがネストされていない場合も消えます。
しかし、上記のどれも役に立ちません。私が実装しているのは、ビュー用の一種の式テンプレートと、タプル、シーケンス、および組み合わせを含む多数の構造に対する遅延評価です。したがって、一時変数への右辺値参照が絶対に必要です。入れ子になったタプル、たとえば(a, (b, c))
、入れ子になった操作を含む式、たとえば、すべてが正常に機能しますu + 2 * v
が、両方ではありません。
上記のコードが有効かどうか、セグメンテーション違反が予想されるかどうか、それを回避する方法、コンパイラと最適化レベルで何が起こっているのかを理解するのに役立つコメントをいただければ幸いです。