すべては、任意のオブジェクトの所有権を引き継ぐ関数を書く必要があるという事実から始まりました。 それはもっと簡単に思えるかもしれません:
template <typename T> void f (T t) { // `t` `T`. ... // — . g(std::move(t)); // — . ... }
ただし、警告が1つあります。受信したオブジェクトは厳密にrvalue
必要があります。 したがって、次のものが必要です。
-
lvalue
を渡そうとしたときにコンパイルエラーを報告します。 - スタック上にオブジェクトを作成するときに、不要なコンストラクター呼び出しを避けてください。
しかし、これはすでに困難です。
説明します。
入力引数の要件
逆の場合、つまり、関数がlvalue
のみをlvalue
、 rvalue
が指定されている場合はコンパイルしないと仮定します。 言語にはこれのための特別な構文があります:
template <typename T> void f (T & t);
このようなレコードは、関数f
がタイプT
オブジェクトへのlvalue
参照を受け入れることを意味しますT
同時に、 cv
修飾子は事前に指定されていません。 これは、定数への参照、非定数への参照、およびその他のオプションになります。
ただし、 rvalue
への参照にすることはできません。 rvalue
への参照を関数f
に渡すと、プログラムはコンパイルされません。
template <typename T> void f (T &) {} int main () { auto x = 1; f(x); // , T = int. const auto y = 2; f(y); // , T = const int. f(6.1); // . }
おそらく、 rvalue
のみを受け入れ、 lvalue
を渡すときにエラーを報告する必要がある場合、反対の場合の構文がありますか?
残念ながら、ありません。
任意のオブジェクトへのrvalue
参照を受け入れる唯一の方法は、 forwarding reference
です。
template <typename T> void f (T && t);
ただし、パススルーリンクは、 rvalue
またはlvalue
へのリンクにすることができます。 したがって、我々はまだ望ましい効果を達成していません。
SFINAE
メカニズムを使用SFINAE
て目的の効果をSFINAE
できますSFINAE
、書き込みと読み取りの両方で非常にかさばり、不便です。
#include <type_traits> template <typename T, typename = std::enable_if_t<std::is_rvalue_reference<T &&>::value>> void f (T &&) {} int main () { auto x = 1; f(x); // . f(std::move(x)); // . f(6.1); // . }
何が本当に好きですか?
この記録が欲しい:
template <typename T> void f (rvalue<T> t);
このエントリの意味は非常に明確に表現されていると思います:任意のrvalue
受け入れます。
最初に思い浮かぶのは、次のようなエイリアスを作成することです。
template <typename T> using rvalue = T &&;
しかし、残念ながら、テンプレート型が出力される前にエイリアス置換が発生するため、このようなことは機能しません。したがって、この状況では、関数引数にrvalue<T>
を記述することはT &&
を記述することと完全に同等です。
面白いのは、Clangコンパイラの型推論システムのエラー(正確なバージョンは覚えていませんが、3.6と思われます)により、このオプションが「機能した」ことです。 GCCコンパイラにはこのエラーがなかったので、最初は頭がおかしいアイデアに曇って、エラーはKlangではなくGetsetsにあると判断しました。 しかし、小さな調査を行った後、これはそうではないことに気づきました。 Klangでしばらくしてから、このエラーは修正されました。
別のアイデア-本質的に同じもの-テンプレートメタプログラミングの愛好家に思い付くかもしれないのは、次のコードを書くことです:
template <typename T> struct rvalue_t { using type = T &&; }; template <typename T> using rvalue = typename rvalue_t<T>::type;
rvalue_t
構造体rvalue_t
は、 rvalue_t
見ることができましたSFINAE
、 T
がlvalue
への参照である場合に落ちるでしょう。
しかし、残念なことに、この考えは失敗する運命にあります。そのような構造は型推論メカニズムを「破る」からです。 その結果、テンプレート引数を明示的に指定しないと、関数f
をまったくf
ません。
私は非常に怒って一時的にこの考えを捨てました。
戻る
今年の初めに、 委員会がC ++ 17標準に概念を含めなかったというニュースがあったとき、私は放棄されたアイデアに戻ることにしました。
少し考えて、「要件」を作成しました。
- 型推論メカニズムが機能するはずです。
- 表示されているタイプに
SFINAE
チェックを設定できるようにする必要があります。
最初の要件は、タイプエイリアスを使用する必要があることをすぐに示唆しています。
その後、論理的な疑問が生じます。タイプエイリアスにSFINAE
を設定することは可能ですか?
それはあなたができることが判明しました。 そして、たとえば次のようになります。
template <typename T, typename = std::enable_if_t<std::is_rvalue_reference<T &&>::value>> using rvalue = T &&;
最後に、必要なインターフェイスと必要な動作の両方を取得します。
template <typename T> void f (rvalue<T>) {} int main () { auto x = 1; f(x); // . f(std::move(x)); // . f(6.1); // . }
勝利
コンセプト
気配りのある読者はinしている:「それでは概念はどこにあるのか?」
しかし、彼が注意深いだけでなく、機知に富んでいる場合、彼はこのアイデアが「概念」にも使用できることをすぐに理解します。 たとえば、次のように:
template <typename I, typename = std::enable_if_t<std::is_integral<I>::value>> using Integer = I; template <typename I> void g (Integer<I> t);
整数の引数のみを受け入れる関数を作成しました。 同時に、結果の構文は、ライターとリーダーの両方にとって非常に快適です。
int main () { g(1); // . g(1.2); // . }
他に何ができますか?
概念の真の構文にさらに近づくことを試みることができます。これは次のようになります。
template <Integer I> void g (I n);
これを行うには、ahem、マクロを使用します。
#define Integer(I) typename I, typename = Integer<I>
次のコードを書くことができます。
template <Integer(I)> void g (I n);
おそらく、この手法の可能性はそこで終わります。
欠点
記事のタイトルを思い出すと、この手法にはいくつかの欠点があると思うでしょう。
はい。 あります。
第一に、概念のオーバーロードを整理することはできません。
コンパイラーは、関数シグニチャーの違いを認識しません
template <typename I> void g (Integer<I>) {} template <typename I> void g (Floating<I>) {}
関数g
オーバーライドに関するエラーをスローします。
第二に、同じタイプの複数のプロパティを同時にテストすることは不可能です。 むしろ、それは可能ですが、すべての読みやすさを無効にする複雑な構造を作成する必要があります。
結論
上記の手法( フィルターエイリアスタイプ手法と呼びます)のスコープはかなり制限されています。
しかし、それが適用可能な場合、プログラマーがコードで意図を明確に表現する非常に良い機会を開きます。
彼女には命の権利があると思う。 個人的に、私はそれを使用します。 そして、私はそれを後悔していません。