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

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

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

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

Q&A

解決済

3回答

2868閲覧

Universal Referenceと通常関数の戻り値型推論とauto&&とdecltype(auto)

yumetodo

総合スコア5852

C++

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

0グッド

0クリップ

投稿2018/10/13 13:07

cpp

1template<typename Container> 2auto&& SelectElementry(Container&& c) { 3 std::uniform_int_distribution<std::size_t> rand(0, c.size() - 1); 4 return c[rand(Random::GetEngine())]; 5}

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

のような関数があったときに、戻り値の型について疑問があります。

cpp

1int main() { 2 const std::vector<int> a = { 2, 6, 8 }; 3 std::vector<int> b { 1, 1, 2 }; 4 static_assert(std::is_same_v<const int&, decltype(SelectElementry(a))>); 5 static_assert(std::is_same_v<int&, decltype(SelectElementry(b))>); 6 std::cout << SelectElementry(a) << "," << SelectElementry(b) << std::endl; 7}

のように戻り値の型を確認していたのですが、SelectElementryの戻り値指定が

  • auto&
  • auto&&
  • decltype(auto)

でまったく同じであることが確認できました。

質問のタイトルに書いたようなキーワードが関連していそうなことは察しがついたので

C++のつまずきポイント解説 その1#テンプレート型推論規則

を読んでみたのですが、いまいちどういう原理で型推論されるのか理解できませんでした。どうなっているのか教えてください。

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

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

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

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

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

guest

回答3

0

ベストアンサー

説明のため、本題に影響しない部分を???で置き換えます。また表記簡略化のため以下std::も省略します。

C++

1template<typename Container> 2??? SelectElementry(Container&& c) { 3 return c[???]; 4}

main関数では関数テンプレートSelectElementryに左辺値(a,b)を渡していますから、テンプレートパラメータContainerはそれぞれconst vector<int>&, vector<int>&に推論されます。return文の式c[???]はそれぞれvector<int>::operator[]() const, vector<int>::operator[]()を呼び出します。つまり式c[???]の型はそれぞれvector<int>::const_reference==const int&, vector<int>::reference==int&となっています。

(下準備ここまで)


戻り値型auto&の場合、const int&型の式からはconst int&が推論され、int&型の式からはint&が推論されます。auto&をテンプレートパラメータT&と読み替えた場合、Tはそれぞれconst int, intに推論されています。

戻り値型auto&&の場合、const int&型の式からはconst int&が推論され、int&型の式からはint&が推論されます。auto&&をテンプレートパラメータT&&と読み替えた場合、Tはそれぞれconst int&, int&に推論されています(これがいわゆるForwarding Reference/Universal Referenceです)。ただし(const) int&+&&はReference Collapsingルールにより(const) int&となるため、最終的には前述のように左辺値参照型となります。(const int&&,int&&のような右辺値参照型には推論されない)

戻り値型decltype(auto)の場合、戻り値型はdelctype(c[???])と推論されるルールです。よってconst int&型の式からはconst int&が推論され、int&型の式からはint&が推論されます。

おまけ:戻り値型autoの場合、const int&, int&型の式いずれからもintが推論されます。(トップレベルのcv修飾は引き継がれず、前者も単にintとなります。)

投稿2018/10/13 15:35

編集2018/10/13 16:10
yohhoy

総合スコア6191

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

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

0

std::vector<bool>を渡すとdecltype(auto)以外はダメですね

c++

1 std::vector<bool> c{true,false,true}; 2 static_assert(std::is_same_v<std::vector<bool>::reference, decltype(SelectElementry(c))>); 3 std::cout << SelectElementry(c) << std::endl;

投稿2018/10/13 14:38

asm

総合スコア15149

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

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

yumetodo

2018/10/13 16:54

decltype(auto)以外だと参照型になって寿命が尽きるから・・・ですか?
asm

2018/10/13 16:58

やってみたらダメだっただけなので何故かはわかんないです・・・
yohhoy

2018/10/15 10:39

FYI: std::vector<bool>は(悪名高い)そのテンプレート特殊化によって、operator[] がbool要素への参照(`bool&`)ではなく、bool要素アクセスのためのプロキシオブジェクトをテンポラリに生成して返します。 戻り値型 auto& つまり非const左辺値参照では、テンポラリオブジェクト(右辺値)を束縛できないためコンパイルエラーです。 戻り値型 auto&& の場合は、テンポラリなプロキシオブジェクトの参照を返す構造になってしまうため、ライフタイム的に未定義動作です。 戻り値型 decltype(auto) の場合は、operator[]の戻り値型=std::vector<bool>::reference となりテンポラリなプロキシオブジェクトがコピー(ムーブ)返却されます(実際にはRVOが有効になります)。
yumetodo

2018/10/15 10:44

ああ、そもそもauto&だと束縛すらできてないのか。
asm

2018/10/15 10:45

右辺値返すからauto&がダメなのはわかってましたが auto&&だと寿命の問題があるんですね
guest

0

こんにちは。

ユニバーサル参照にlvalueを渡しているので、パラメータcの型はconst、非constのどちらの場合も参照になります。リンク先の下記原理ですね。

T&を仮引数にとる関数にconst修飾された型を渡すとconstは型の一部として扱われます

参照は読み飛ばされます

つまり、int const&を渡すとTはint const、int&を渡すとTはintということでしょう。
ユニバーサル参照は lvalue を渡すと参照になるので、結果上述したようになります。

std::vectorのoperator[]は参照(もしくは、const参照)を返却しますので、c[x]は参照かconst参照になります。それを auto&で受けた時は素直に参照になります。auto&&(ユニバーサル参照)にlvalueを渡すと参照になります。decltype(auto)は参照を受け取ると参照になるようです。

投稿2018/10/13 13:59

Chironian

総合スコア23272

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

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

yohhoy

2018/10/13 14:49

「参照は読み飛ばされます」のくだりは「テンプレート型推論その1(パラメータタイプが参照もしくはポインタ型」の説明ですから、本ケースでは引用元として不適切に思います。
Chironian

2018/10/13 16:31

yohhoyさん 確かに! ユニバーサル参照の話題だったので、T&&と読み違ってました。いい加減に読んでいるとだめですね。 ご指摘ありがとうございます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.37%

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

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

質問する

関連した質問