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

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

ただいまの
回答率

90.51%

  • C++

    3468questions

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

  • C++14

    41questions

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

解決済

回答 3

投稿

  • 評価
  • クリップ 0
  • VIEW 527

yumetodo

score 2290

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

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

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

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

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

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

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

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

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

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

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

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

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

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 3

checkベストアンサー

+4

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

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

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

(下準備ここまで)


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

戻り値型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となります。)

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

+1

こんにちは。

ユニバーサル参照に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 23:49

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

    キャンセル

  • 2018/10/14 01:31

    yohhoyさん

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

    キャンセル

+1

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

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

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/10/14 01:54

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

    キャンセル

  • 2018/10/14 01:58

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

    キャンセル

  • 2018/10/15 19:39

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

    キャンセル

  • 2018/10/15 19:44

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

    キャンセル

  • 2018/10/15 19:45

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

    キャンセル

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

  • ただいまの回答率 90.51%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る

  • C++

    3468questions

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

  • C++14

    41questions