32

C++11 (および C++14) では、ジェネリック プログラミングを対象とする追加の言語構造と改善が導入されています。これらには次のような機能が含まれます。

  • R値参照
  • 参照の崩壊
  • 完全転送
  • 移動セマンティクス、可変個引数テンプレートなど

私はC++14 仕様の以前のドラフト(現在は更新されたテキストを含む) と§20.5.1の例のコードを閲覧していました。

template<class F, class Tuple, std::size_t... I>
decltype(auto) apply_impl(F&& f, Tuple&& t, index_sequence<I...>) {
  return std::forward<F>(f)(std::get<I>(std::forward<Tuple>(t))...);
}

template<class F, class Tuple>
decltype(auto) apply(F&& f, Tuple&& t) {
  using Indices = make_index_sequence<std::tuple_size<Tuple>::value>;
  return apply_impl(std::forward<F>(f), std::forward<Tuple>(t), Indices());
}

オンラインはこちら[intseq.general]/2 .

質問

  • 関数が転送された理由、つまりなぜfですか?apply_implstd::forward<F>(f)(std::get...
  • 関数をそのまま適用しないのはなぜf(std::get...ですか?
4

2 に答える 2

49

簡単に言えば...

TL;DR、ファンクターの値カテゴリ(r 値/l 値の性質) を保持する必要があるのは、これがオーバーロードの解決、特にref 修飾されたメンバーに影響を与える可能性があるためです。

関数定義の削減

関数が転送される問題に焦点を当てるために、サンプルを次のように縮小しました (そして C++11 コンパイラでコンパイルしました)。

template<class F, class... Args>
auto apply_impl(F&& func, Args&&... args) -> decltype(std::forward<F>(func)(std::forward<Args>(args)...)) {
  return std::forward<F>(func)(std::forward<Args>(args)...);
}

std::forward(func)そして、2 番目のフォームを作成します。ここでは、を justに置き換えfuncます。

template<class F, class... Args>
auto apply_impl_2(F&& func, Args&&... args) -> decltype(func(std::forward<Args>(args)...)) {
  return func(std::forward<Args>(args)...);
}

サンプル評価

これがどのように動作するか (準拠するコンパイラを使用して) のいくつかの経験的証拠を評価することは、コード例がそのように記述された理由を評価するための適切な出発点です。したがって、さらに一般関手を定義します。

struct Functor1 {
  int operator()(int id) const
  {
    std::cout << "Functor1 ... " << id << std::endl;
    return id;
  }
};

初期サンプル

サンプル コードを実行します。

int main()
{
  Functor1 func1;
  apply_impl_2(func1, 1);
  apply_impl_2(Functor1(), 2);
  apply_impl(func1, 3);
  apply_impl(Functor1(), 4);
}

また、オーバーロードされた呼び出し演算子が呼び出されたときにFunctor1()、右辺値が使用されるか左辺値が使用されるかに関係なく、出力は期待どおりです。これは、r 値と l 値の両方に対して呼び出されます。C++03 では、オブジェクトの "r-value-ness" または "l-value-ness" に基づいてメンバー メソッドをオーバーロードすることはできませんでした。funcapply_implapply_impl_2

ファンクター1 ... 1
ファンクター1 ... 2
ファンクター1 ... 3
ファンクター1 ... 4

参照適格サンプル

これをもう少し拡張するために、その呼び出し演算子をオーバーロードする必要があります...

struct Functor2 {
  int operator()(int id) const &
  {
    std::cout << "Functor2 &... " << id << std::endl;
    return id;
  }
  int operator()(int id) &&
  {
    std::cout << "Functor2 &&... " << id << std::endl;
    return id;
  }
};

別のサンプル セットを実行します。

int main()
{
  Functor2 func2;
  apply_impl_2(func2, 5);
  apply_impl_2(Functor2(), 6);
  apply_impl(func2, 7);
  apply_impl(Functor2(), 8);
}

出力は次のとおりです。

ファンクター 2 &... 5
ファンクター 2 &... 6
ファンクター 2 &... 7
ファンクター 2 &&... 8

討論

apply_impl_2( 5 および 6)の場合id、出力は最初に期待されたものとは異なります。どちらの場合も、修飾operator()された左辺値が呼び出されます (右辺値はまったく呼び出されません)。r-value が修飾された r-valueFunctor2()を呼び出すために使用されているため、呼び出されることが予想されていた可能性があります。への名前付きパラメーターとしてのは右辺値参照ですが、名前が付けられているため、それ自体が左辺値です。したがって、修飾された左辺値は、左辺値が引数である場合と右辺値が引数として使用される場合の両方で呼び出されます。apply_impl_2operator()funcapply_impl_2operator()(int) const&func2Functor2()

apply_impl( id7 および 8) の場合、 はに提供された引数の右辺値/左辺値の性質をstd::forward<F>(func) 維持または保存funcします。したがって、修飾された左辺値は、左辺値が引数として使用され、右辺値が引数として使用される場合は右辺値が修飾されてoperator()(int) const&呼び出されます。この動作は、予期されていたものです。func2operator()(int)&&Functor2()

結論

完全転送によるの使用により、の元の引数の右辺値/左辺値の性質std::forwardが確実に保持されます。値カテゴリを保持します。func

関数に引数を転送std::forwardするだけでなく、右辺値/左辺値の性質を保持する必要がある場合に引数を使用する必要がある場合にも、必須であり、使用でき、また使用する必要があります。ノート; r値/l値を保存できない、または保存すべきでない状況があり、これらの状況では使用しないでください(以下の逆を参照)。std::forward

右辺値参照の一見無害な使用によって、引数の右辺値/左辺値の性質を不注意に失う多くの例がポップアップ表示されます。

明確に定義された健全な汎用コードを記述することは常に困難でした。右辺値参照の導入、特に参照の折りたたみにより、より優れたジェネリック コードをより簡潔に記述できるようになりましたが、提供される引数の本来の性質が何であるかをこれまで以上に認識し、次のことを確認する必要があります。それらは、私たちが作成する汎用コードで使用するときに維持されます。

完全なサンプルコードはここにあります

系と逆

  • 質問の結果は次のようになります。テンプレート化された関数で参照が崩壊する場合、引数の右辺値/左辺値の性質はどのように維持されますか? 答え - を使用しますstd::forward<T>(t)
  • コンバース; std::forwardすべての「ユニバーサル リファレンス」の問題を解決できますか? いいえ、値を複数回転送するなど、使用してはいけない場合があります。

転送を完璧にするための簡単な背景

完全転送になじみのない人もいるかもしれませんが、完全転送とは何ですか?

簡単に言えば、完全な転送とは、関数に提供された引数が、最初に提供されたものと同じ値カテゴリ (基本的に r 値と l 値) を持つ別の関数に転送 (渡される) ことを保証するためにあります。これは通常、参照の崩壊が発生した可能性があるテンプレート関数で使用されます。

Scott Meyers は、Going Native 2013 のプレゼンテーションstd::forwardで次の疑似コードを示し、 (約 20 分の時点で)の仕組みを説明しています。

template <typename T>
T&& forward(T&& param) { // T&& here is formulated to disallow type deduction
  if (is_lvalue_reference<T>::value) {
    return param; // return type T&& collapses to T& in this case
  }
  else {
    return move(param);
  }
}

完全な転送は、現在ジェネリック プログラミングで見られるものの多くの基礎を形成する、C++11 に新しく追加されたいくつかの基本的な言語構造に依存しています。

  • 参照の崩壊
  • 右辺値参照
  • セマンティクスの移動

の使用std::forwardは現在、定型的な で意図されていstd::forward<T>ます。どのようにstd::forward機能するかを理解することは、なぜこれがそのようなものなのかを理解するのに役立ち、右辺値、参照の崩壊、同類の非慣用的または不適切な使用を特定するのにも役立ちます。

Thomas Becker は、完全な転送の問題解決策について、素晴らしく、しかし詳細な記事を提供しています。

参照修飾子とは何ですか?

ref-qualifiers (左辺値 ref-qualifier&および rvalue ref-qualifier ) は、オーバーロードの解決中にどのメソッドを呼び出すかを決定するために ( ref-qualified members ) が使用される&&という点で cv-qualifiers に似ています。期待どおりに動作します。これは左辺値と右辺値に適用されます。注: cv 修飾とは異なり、左辺値式のままです。&&&*this

于 2014-07-16T11:41:24.230 に答える