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

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

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

C++11は2011年に容認されたC++のISO標準です。以前のC++03に代わるもので、中枢の言語の変更・修正、標準ライブラリの拡張・改善を加えたものです。

C++

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

Q&A

解決済

2回答

2314閲覧

戻り値のdecltype(auto)について、ユニバーサル参照について

__ook

総合スコア49

C++11

C++11は2011年に容認されたC++のISO標準です。以前のC++03に代わるもので、中枢の言語の変更・修正、標準ライブラリの拡張・改善を加えたものです。

C++

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

1グッド

1クリップ

投稿2020/04/09 02:31

以下のようなコードを書きました。

cpp

1 2#include <iostream> 3#include <vector> 4#include <functional> 5#include <algorithm> 6#include <tuple> 7#include <type_traits> 8 9template <typename T> 10decltype(auto) ret(T&& val) { 11 return val; 12} 13 14template <typename T> 15auto cout_check(T& val) { 16 std::cout << "左辺値" << std::endl; 17} 18 19template <typename T> 20auto cout_check(T&& val) { 21 std::cout << "左辺値" << std::endl; 22} 23 24 25int main() 26{ 27 // T から T&&に変換できないとは? 28 int i = 0; 29 cout_check(ret(i)); 30 cout_check(ret(0)); 31} 32

T から T&&に変換できないというエラーが出ました。
僕の予想だと、T&&でユニバーサル参照としてどちら右辺値左辺値どちらも対応が可能で、decltype(auto)によりそれぞれの参照が返され、
"左辺値"
"右辺値"
と出力されると思っていました。

また、これも疑問なのですが、
auto cout_check(T&& val)と書いた場合、この場合、右辺値を引数とすることを考えているのですが、
ユニバーサル参照と右辺値参照は文法が同じ?ため、どちらでも参照できてしまう関数になってしまうかと思います。

明示的に左辺値参照での引数受け取り、右辺値参照での引数受け取りをしたい場合はこの書き方(cout_checkのような)でいいのでしょうか。

yumetodo👍を押しています

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

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

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

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

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

guest

回答2

0

ベストアンサー

まあそもそもcout_checkの中身両方左辺値って書いてますやん、というツッコミはさておき本題へ。

端的に言うと、retのreturn文をreturn std::forward<T>(val);にすれば通ります。

C++でもっともお手軽に型を確認する手段である、[[deprecated]] attributeをつけて確認してみましょう。

cpp

1#include <iostream> 2#include <vector> 3#include <functional> 4#include <algorithm> 5#include <tuple> 6#include <type_traits> 7 8template <typename T> 9[[deprecated]] decltype(auto) ret(T&& val) { 10 return std::forward<T>(val); 11 //return val; 12} 13 14template <typename T> 15auto cout_check(T&) { 16 std::cout << "左辺値" << std::endl; 17} 18 19template <typename T> 20auto cout_check(T&&) { 21 std::cout << "右辺値" << std::endl; 22} 23int f() { return 3; } 24 25int main() 26{ 27 // T から T&&に変換できないとは? 28 int i = 0; 29 cout_check(ret(i)); 30 cout_check(ret(0)); 31 cout_check(ret(f())); 32}

https://wandbox.org/permlink/2bW402AZYXRdO3Qt

prog.cc:29:16: warning: 'ret<int &>' is deprecated [-Wdeprecated-declarations] cout_check(ret(i)); ^ prog.cc:9:3: note: 'ret<int &>' has been explicitly marked deprecated here [[deprecated]] decltype(auto) ret(T&& val) { ^ prog.cc:30:16: warning: 'ret<int>' is deprecated [-Wdeprecated-declarations] cout_check(ret(0)); ^ prog.cc:9:3: note: 'ret<int>' has been explicitly marked deprecated here [[deprecated]] decltype(auto) ret(T&& val) { ^ prog.cc:31:16: warning: 'ret<int>' is deprecated [-Wdeprecated-declarations] cout_check(ret(f())); ^ prog.cc:9:3: note: 'ret<int>' has been explicitly marked deprecated here [[deprecated]] decltype(auto) ret(T&& val) { ^ 3 warnings generated. 左辺値 右辺値 右辺値

retにlvalueを渡したときは確かに引数の型はint&になっていて、rvalueを渡したときはint&&になっていることが確認できます。

じゃあなぜだめだったのか?もっとシンプルなコードを見ていきましょう。

cpp

1#include <type_traits> 2int main() 3{ 4 int&& r = 3; 5 static_assert(std::is_same_v<decltype(r), int&&>); 6 decltype(auto) aa = r; 7}

https://wandbox.org/permlink/dNlWQiBpA0v6UxcJ

prog.cc:6:20: error: rvalue reference to type 'int' cannot bind to lvalue of type 'int' decltype(auto) aa = r; ^ ~ 1 error generated.

つまりどういうことかと言うと、int&&型の変数はlvalueだと言うことです。

ではstd::forwardが入るとどうなるのか?std::forwardstatic_cast<T&&>(t)を返すのでした。

rvalue referenceへのキャスト式の評価結果はrvalueであり、rvalue referenceを返す関数の呼び出しの評価結果はrvalueであることを利用します。

順を追ってコードを書き換えていきましょう。

cpp

1#include <type_traits> 2int main() 3{ 4 int&& r = 3; 5 static_assert(std::is_same_v<decltype(r), int&&>); 6 [[maybe_unused]] decltype(auto) aa = static_cast<int&&>(r); 7}

https://wandbox.org/permlink/vTpYLaRos85YbmOm

このようにするとコンパイルが通ります。なぜならばrvalue referenceへのキャスト式の評価結果はrvalueだからです。

cpp

1#include <type_traits> 2#include <utility> 3int main() 4{ 5 int&& r = 3; 6 static_assert(std::is_same_v<decltype(r), int&&>); 7 [[maybe_unused]] decltype(auto) aa = std::forward<int>(r); 8}

https://wandbox.org/permlink/9NQcbjeMBFkf3rLY

このコードもコンパイルが通ります。なぜならばrvalue referenceを返す関数の呼び出しの評価結果はrvalueだからです。

もっと全体的に知りたい場合は
みんなlvalueとrvalueを難しく考えすぎちゃいないかい? - Qiita
をどうぞ。

投稿2020/04/09 03:46

編集2020/04/09 03:50
yumetodo

総合スコア5850

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

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

__ook

2020/04/09 04:26

あっ、コンパイルすら通っておらず気づいてませんでしたね…ありがとうございます。 完全転送しないとだめなのですね。記事再度拝見します。ありがとうございます。
guest

0

間違いやすくて重要なことなんですが右辺値参照は左辺値です。 なのでこの場合には decltype(auto)int& と推論されます。

右辺値参照・左辺値参照の振り分けを伝播させるために std::forward があるのでこれを用いてください。

cout_check についてはそのようなやり方で問題ありません。

ただ、左辺値参照・右辺値参照の振り分けが質問者の意図通りにはならないにせよエラーになるのはおかしいですね。 コンパイラのバグかもしれません。

投稿2020/04/09 03:39

編集2020/04/09 03:41
SaitoAtsushi

総合スコア5444

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

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

yumetodo

2020/04/09 03:47

いや、単にそもそもcout_checkの中身両方左辺値って書いてるせいだと思います。
SaitoAtsushi

2020/04/09 03:50

いや Visual Studio ではコンパイルエラーになるんですよ。
SaitoAtsushi

2020/04/09 03:58

質問者が書いている通りのコードであればという話ですよ。 振り分けが上手くいかないにしてもコンパイルエラーになるのはおかしいでしょうと言っています。
yumetodo

2020/04/09 04:06 編集

error C2440: 'return': cannot convert from 'T' to 'T &&' のことだと思いますが、 https://godbolt.org/z/WDkLLx どちらかと言うともとのコードに-Wall -Wextra -pedanticつけても何も言わないgccのほうがおかしくないですか? lvalueをrvalue referenceが束縛できたらおかしいじゃないですか。
__ook

2020/04/09 04:27

回答ありがとうございます。 振り分けができないにしろコンパイルエラーなのは右辺値参照へのキャストができないから、でしょうか…? 完全転送すると問題なく動作しました。ありがとうございます。
SaitoAtsushi

2020/04/09 05:38

束縛できるのはおかしいという点には同意しますが、 decltype(auto) での推論結果に差があり私は gcc の推論の方が正しいと思っていたのでそこに齟齬があるようです。 右辺が右辺値参照だった場合に gcc では decltype(auto) が左辺値参照になりますが clang や vs では右辺値参照になっています。 これは gcc の方が間違いですかね?
yumetodo

2020/04/09 05:49

なるほど、それはちょっとじっくり検討してみます。
yumetodo

2020/04/09 07:06

https://timsong-cpp.github.io/cppwp/n4140/dcl.spec.auto#7 より、関数の戻り値におけるdecltype(auto)指定は単にreturn文をdecltypeで評価したものであり、例えば()で囲われてから評価されたりはしません(decltype(val)であってdecltype((val))ではない)。 valはan unparenthesized id-expressionに該当しますから https://timsong-cpp.github.io/cppwp/n4140/dcl.type.simple#4.1 のルールが適用され、int&&となるのが正しいと考えられます。 つまり、やはりGCCのバグでいいと思います。 バグレポは出ているのですがずっと放置されてます。 https://gcc.gnu.org/bugzilla/show_bug.cgi?id=64892
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問