質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
参照

参照は、プログラミングにおいて変数や関数といったメモリ空間上での所在を指示するデータのことを指します。その中にはデータ自体は含まれず、他の場所にある情報を間接的に指示するプログラムです。

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

Q&A

1回答

712閲覧

C++ での可変引数テンプレートの扱い方と右辺値参照、std::forward について教えてください

pdd

総合スコア0

参照

参照は、プログラミングにおいて変数や関数といったメモリ空間上での所在を指示するデータのことを指します。その中にはデータ自体は含まれず、他の場所にある情報を間接的に指示するプログラムです。

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

1グッド

0クリップ

投稿2022/08/30 08:49

C++17で printf のラッパーのようなものを書こうとしています。
cpprefjp の可変引数テンプレートのページを見ると以下のコードがありました。疑問点は2つです。

  1. print() の引数が右辺値参照なのはなぜですか? std::string などをコピーコストなしで扱いたいなら const 参照でよいのでは、と思いました。
  2. print(std::forward<Args>(args)...) の部分はどういう意味ですか? print(args...) と書くのと何が違いますか?

cpp

1#include <iostream> 2#include <utility> 3 4// パラメータパックが空になったら終了 5void print() {} 6 7// ひとつ以上のパラメータを受け取るようにし、 8// 可変引数を先頭とそれ以外に分割する 9template <class Head, class... Tail> 10void print(Head&& head, Tail&&... tail) 11{ 12 std::cout << head << std::endl; 13 14 // パラメータパックtailをさらにheadとtailに分割する 15 print(std::forward<Tail>(tail)...); 16} 17 18int main() 19{ 20 print(1, 'a', "hello"); 21}

上のコードを次のように書き換えたところ、コンパイルできて同じように実行もできているようです。この書き方ではダメな理由があるのでしょうか?

cpp

1#include <iostream> 2#include <utility> 3 4// パラメータパックが空になったら終了 5void print() {} 6 7// ひとつ以上のパラメータを受け取るようにし、 8// 可変引数を先頭とそれ以外に分割する 9template <class Head, class... Tail> 10void print(const Head& head, const Tail&... tail) 11{ 12 std::cout << head << std::endl; 13 14 // パラメータパックtailをさらにheadとtailに分割する 15 print(tail...); 16} 17 18int main() 19{ 20 print(1, 'a', "hello"); 21}
fana👍を押しています

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

fana

2022/08/30 09:30

詳しくないので回答として書けませんが,それば右辺値参照ではなくて「ユニバーサル参照」とかいうやつらしいです. で,こいつが実際は右辺値参照か左辺値参照かわからんから(?)それをそのまま次に伝達するために forward しているんじゃないかと.
fana

2022/08/30 09:35

ということは,print() の引数に右辺値(右辺値参照? 言葉がどうにも…)を使わないのであれば下側のコードで良い,とかいうことになるのかな?
pdd

2022/08/30 10:02

ありがとうございます。ユニバーサル参照について調べてみます。
pdd

2022/08/30 12:10

調べたところ、 ・const 参照もユニバーサル参照も、左辺値と右辺値の両方を引数にとることができる ・とった引数を、std::string のコンストラクタのように左辺値か右辺値かで呼び分けるような関数の引数に渡すとき、ユニバーサル参照でかつ std::forward を用いると、右辺値参照は右辺値参照としてそのまま渡すことができる ようです。 左辺値と右辺値で違う処理をする関数に渡す場合でなければ、const 参照で問題なさそう? という理解をしました。
ttact

2022/08/30 22:49

https://cpplover.blogspot.com/2009/11/rvalue-reference_23.html 「規格には特別なルールがあり、テンプレート引数を、rvalue referenceとして関数の引数に使った場合のargument deductionで、lvalueを渡すと、lvalue referenceとなるのである。」 「もし、これが出来ないとなると、プログラマは、わざわざ、lvalue referenceとrvalue referenceとで、似たようなコードを複数書かなければならなくなる。すべての組み合わせを網羅するには、膨大なオーバーロード関数が必要になる。」 ということで、全部constなlvalueで書ける内容であれば、わざわざuniversal referenceにする必要はないです。経験上、超汎用的なライブラリであればあるほどuniversal referenceにする要件は高まりますし、アプリケーションレイヤの具体的な機能に近づけば近づくほどその要件は低くなると感じます。
pdd

2022/08/31 12:15

ありがとうございます。リンク先がとても参考になりました。
guest

回答1

0

引数が右辺値参照なのはなぜですか?

この文脈では && は右辺値参照とは限りません。 右辺値参照で左辺値を受け取ると参照の折り畳みという現象が起こり、左辺値参照になります。 参照崩壊という訳語があてられることもあります。 また、言語仕様上の用語ではありませんが、このような参照のことを万能参照 (universal reference) という言葉で説明しているものが多いのでそういった言葉をキーワードにして調べてみればよいでしょう。

コピーコストなしで扱いたいなら const 参照でよいのでは

その通りです。 ここでは const 参照で問題ありません。

「パラメータパックを転送するような状況で必要となる」という但し書きが付いた説明なのでより一般的な場合の例として書いているだけだと思います。

print(std::forward<Args>(args)...) の部分はどういう意味ですか? print(args...) と書くのと何が違いますか?

これは非常にわかり難いのですが右辺値参照は左辺値です

つまり tail が右辺値参照だった場合に print に渡すと左辺値として渡すことになってしまいます。 左辺値参照なら左辺値として、右辺値参照なら右辺値として渡すためにキャストしてくれるのが std::forward の役目です。 tail が左辺値の可能性も右辺値の可能性もあるときに振り分けなおすのが用途なので普通はテンプレートの中にしか現れません。

投稿2022/08/30 14:42

SaitoAtsushi

総合スコア5444

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

pdd

2022/08/30 17:16

ありがとうございます。 右辺値として扱って特別な処理をしたい場合(何かのムーブコンストラクタに渡すなど?)でなければ、ユニバーサル参照と std::forward を使う必要はないという理解で良いでしょうか。
SaitoAtsushi

2022/08/31 01:39

あなたが考える特別な処理 (と特別でないものの区別) が何であるかわからないのでそれが正しいかどうか私にはわかりません。 ・ 参照には左辺参照と右辺参照がある ・ その上で左辺参照と右辺参照を統一的に扱えるインターフェイスとして万能参照や std::forwad がある ので、これが必要であれば使うべき場面ですし、そうでないなら使いません。
pdd

2022/08/31 12:13

左辺値参照と右辺値参照を統一的に扱う例として、std::string のコンストラクタのような、左辺値か右辺値かによってコンパイラが呼び分けてくれる関数に渡すという状況を挙げたつもりでした。 右辺値参照を右辺値参照のまま扱いたいのでなければ使う必要はないと理解しました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだベストアンサーが選ばれていません

会員登録して回答してみよう

アカウントをお持ちの方は

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問