C++17で printf のラッパーのようなものを書こうとしています。
cpprefjp の可変引数テンプレートのページを見ると以下のコードがありました。疑問点は2つです。
- print() の引数が右辺値参照なのはなぜですか? std::string などをコピーコストなしで扱いたいなら const 参照でよいのでは、と思いました。
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}
詳しくないので回答として書けませんが,それば右辺値参照ではなくて「ユニバーサル参照」とかいうやつらしいです.
で,こいつが実際は右辺値参照か左辺値参照かわからんから(?)それをそのまま次に伝達するために forward しているんじゃないかと.
ということは,print() の引数に右辺値(右辺値参照? 言葉がどうにも…)を使わないのであれば下側のコードで良い,とかいうことになるのかな?
ありがとうございます。ユニバーサル参照について調べてみます。
調べたところ、
・const 参照もユニバーサル参照も、左辺値と右辺値の両方を引数にとることができる
・とった引数を、std::string のコンストラクタのように左辺値か右辺値かで呼び分けるような関数の引数に渡すとき、ユニバーサル参照でかつ std::forward を用いると、右辺値参照は右辺値参照としてそのまま渡すことができる
ようです。
左辺値と右辺値で違う処理をする関数に渡す場合でなければ、const 参照で問題なさそう? という理解をしました。
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にする要件は高まりますし、アプリケーションレイヤの具体的な機能に近づけば近づくほどその要件は低くなると感じます。
ありがとうございます。リンク先がとても参考になりました。