13

次のコード スニペットを考えてみましょう。これは、推定される型に特化したクラス テンプレートのインスタンスを関数テンプレートに構築させるという一般的なイディオムを使用しています。たとえば、std::make_uniqueandstd::make_tupleで見られます。

template <typename T>
struct foo
{
    std::decay_t<T> v_;
    foo(T&& v) : v_(std::forward<T>(v)) {}
};

template <typename U>
foo<U> make_foo(U&& v)
{
    return { std::forward<U>(v) };
}

Scott Meyers の「ユニバーサル リファレンス」のコンテキストでは、引数 to はmake_fooユニバーサル リファレンスです。のコンストラクターへの引数は、その型は ですが、(一般に) 推定されないため、ユニバーサル参照ではありません。U&&UfooT&&T

しかし、 のコンストラクターがfooによって呼び出される場合、 のコンストラクターへの引数をユニバーサル参照であるmake_fooと考えるのは理にかなっているように思えます。関数 template によって推定されているからです。の型が両方の関数で同じになるように、同じ参照折りたたみルールが適用されます。この場合、 と の両方が演繹された と言えます。fooTmake_foovTU

だから私の質問は2つあります:

  • 私の例のように、呼び出し元によってユニバーサル参照コンテキスト内で推定されfooた限られたケースで、コンストラクターへの引数をユニバーサル参照であると考えるのは理にかなっていますか?T
  • 私の例では、両方ともstd::forward賢明な使用ですか?
4

2 に答える 2

15

make_foo「正しい」と同じ球場にありますが、fooそうではありません。fooコンストラクターは現在、推定されていない のみを受け入れ、T &&そこに転送することはおそらくあなたの意図とは異なります (ただし、@nosid のコメントを参照してください)。全体としてfoo、型パラメーターを取り、テンプレート化されたコンストラクターを持ち、メーカー関数が減衰を行う必要があります。

template <typename T>
struct foo
{
    T v_;

    template <typename U>
    foo(U && u) : v_(std::forward<U>(u)) { }
};

template <typename U>
foo<typename std::decay<U>::type> make_foo(U && u)
{
    return foo<typename std::decay<U>::type>(std::forward<U>(u));
}

C++14 では、maker 関数の記述が少し簡単になります。

template <typename U>
auto make_foo(U && u)
{ return foo<std::decay_t<U>>(std::forward<U>(u)); }

あなたのコードが今書かれているようにint a; make_foo(a);、タイプのオブジェクトを作成しますfoo<int &>。これは内部に を格納しますintが、そのコンストラクターは引数のみを受け入れint &ます。対照的に、make_foo(std::move(a))を作成しfoo<int>ます。

したがって、あなたが書いたように、クラステンプレートの引数はコンストラクターの署名を決定します。(これstd::forward<T>(v)は、倒錯した方法で意味をなします(これを指摘してくれた@nodisに感謝します)が、これは間違いなく「転送」ではありません。)

それは非常に珍しいことです。通常、クラス テンプレートは関連するラップされた型を決定する必要があり、コンストラクターはラップされた型を作成するために使用できるものをすべて受け入れる必要があります。つまり、コンストラクターは関数テンプレートである必要があります。

于 2014-06-30T19:18:40.257 に答える
2

「ユニバーサル リファレンス」の正式な定義はありませんが、次のように定義します。

ユニバーサル参照は、タイプ [template-parameter] の関数テンプレートのパラメーターであり、&&テンプレート パラメーターは関数の引数から推測でき、引数は必要に応じて左辺値参照または右辺値参照のいずれかによって渡されます。

したがって、その定義により、いいえ、のコンストラクターのT&& vパラメーターはfooユニバーサル参照ではありません。

ただし、「ユニバーサル リファレンス」という言葉の要点は、コードを設計、読み取り、理解する際に人間が考えるモデルまたはパターンを提供することです。make_fooまた、「のコンストラクターを呼び出すと、コンストラクター パラメーターが必要に応じて左辺値参照または右辺値参照のいずれかになるようfoo<U>に、テンプレート パラメーターTが引数から推定されている」と言うのは合理的で役立ちます。これは、「のコンストラクターを呼び出すとき、コンストラクター パラメーターは本質的にユニバーサル リファレンスである」という主張に移っても問題ないという同じ概念に十分に近いものです。make_fooT&& vmake_foofoo<U>T&& v

はい、両方の使用は、std::forwardここで意図したことを実行し、可能であればメンバーが引数v_から移動するか、そうでない場合はコピーできるようにします。make_fooしかし、 のコピーを含むではなくをmake_foo(my_str)返すことは、非常に驚​​くべきことです....foo<std::string&>foo<std::string>my_str

于 2014-06-30T19:56:14.390 に答える