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

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

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

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

Q&A

解決済

3回答

2634閲覧

decltypeの不思議

asobinin

総合スコア69

C++

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

1グッド

1クリップ

投稿2019/06/06 15:30

decltypeには不思議がいっぱいです。
その驚くほど複雑な仕様は、彼ら闇魔術師の足元にも及ばない私には大変理解し難いものです。

以下のコードにあるdecltype((a))はその仕様のうちの一つです。

Cpp

1#include <iostream> 2 3int main() { 4 int a = 2; // int 5 decltype(a) b = a; // int 6 decltype((a)) c = a; // int& ←こ↑こ↓ 7 ++c; 8 decltype(auto) d = ++a; // int& 9 ++d; 10 11 cout << a << " " << b << " " << c << " " << d << endl; 12 13 return 0; 14} 15

何故()を付けると、参照型だと推論されてしまうのでしょうか。

decltype(auto)に関しては、「Effective Modern C++」という魔導書を読んだことで辛うじて理解できています・・・
トラックに轢かれても、彼らの世界にだけは転生したくありません。

fa11enprince👍を押しています

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

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

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

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

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

guest

回答3

0

C++11で導入されたdecltypeは、2003年の n1478 初版から最終的に2007年の n2343 (Revision 7)まで、長い期間議論された後に正式採用された言語機能だったようです。

n2115 (Revision 6) によれば、「変数名に対してはソースコード上で宣言している型」を直接探し出しますが、「ソースコードに登場しない式では左辺値(lvalue)は参照型とする」とあります。

2.2 Semantics of decltype
Determining the type decltype(e) build on a single guiding principle: look for the declared type of the expression e. If e is a variable or formal parameter, or a function/operator invocation, the programmer can trace down the variable's, parameter's, or function's declaration, and find the type declared for the particular entity directly from the program text. This type is the result of decltype. For expressions that do not have a declaration in the program text, such as literals and calls to built-in operators, lvalueness implies a reference type.

この考え方を質問中コードに適用すると、次の通りとなります。

c++

1int a = 2; 2 3decltype(a) b = a; 4// 式a つまり 変数a はint型として宣言される → int 5 6decltype((a)) c = a; 7// 式(a) の宣言はソースコードに出現しない → int&

変数aに対してdecltype(a)decltype((a))をどのように扱うかについては、C++11での正式採用に向けて議論を重ねる中でも変遷があったようです。

  • 2004年 n1705 (Revision 4)では「decltype((e))decltype(e)と同じと明記する」
  • 2006年 n2115 (Revision 6)では「decltype((e))decltype(e)は別扱いとする」
  • 最終的に「decltype((e))decltype(e)は別扱い」のまま決着したようです。

投稿2019/06/07 09:10

編集2019/06/07 13:05
yohhoy

総合スコア6191

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

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

asobinin

2019/06/07 10:00

「C++の仕様はころころ変わる」というのは本当だったのですね・・・ たった一つの機能にそれだけの時間を費やすということは、それほどに今後decltypeが活躍してくるということでしょうか。深淵を覗くのは他の人に任せます。
yohhoy

2019/06/07 10:04

回答中で取り上げたのは「言語仕様策定プロセス中の経緯」ですから、一般プログラマがふれるC++仕様はころころは変わりませんよ。 過去資産ソースコードを壊さないという意味で、C++の仕様はそこそこ安定していると思います。たった一つの機能にも多くの議論を重ねるのは、一度正式に入れてしまった機能は簡単には変えることができないことの裏返しですね。
guest

0

Chironian さんの回答の補足です。

何故()を付けると、参照型だと推論されてしまうのでしょうか

Chironian さんの回答の通り、decltypeの仕様として、そのように定義されているからです。decltypeは、その引数の式の型となるとは限りません。

この場合は、decltype 指定子 - cppreference.com における 説明 4) が適用されています。つまり、decltype((a))で、引数(a)はかっこで囲まれているint型のlvalue式なので、int&と推論されています。

ですので、rvalue referenceとは直接関係ないと思われます。

(式が参照型を持つことはありません。C++ の式は、それぞれ2つの独立した性質、型および値カテゴリによって特徴付けられていますが、式が最初に型Tへの参照を持つ場合、その型はそれ以降の分析の前にTに調整されます。)

(また、lvalue, rvalueなどついては、値カテゴリ - cppreference.com が詳しいです。)

投稿2019/06/07 04:32

alphya

総合スコア124

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

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

Chironian

2019/06/07 07:12 編集

alphyaさん、ありがとうございます。 大きな勘違いしていたようです。回答に追記しました。
guest

0

ベストアンサー

こんにちは。

decltypeの訓練はご存知でしょうか? これは比較的分かりやすい解説と思います。
eに括弧がない時はdecltype(e)はeの型そのもの(もっと条件は複雑ですが略)、中略、「eがlvalueならば、decltype(e)はT &である。Tはeの型である」と定義されているからのようです。

(a)は式ですから、一時領域(rvalue reference)を返します。rvalue referenceはlvalueなので後半の条件に該当しT&となるという寸法のようです。

さて、「rvalue referenceはlvalue」が謎ですよね。「お前は何を言っているんだ?」と感じます。
以前、拙いですがところで右辺値参照は左辺値ですで解説したことがあります。
分かりやすいとは言えませんが、多少はヒントになるかも知れません。


【alphyaさんの回答を見て修正】
(a)はてっきり一時領域を返すと思っていたのですが、(例えばstd::vectorのoperator[]やatと同様)これの結果は左辺値参照と理解しておけば良さそうです。(a)=123;通りました。(一時領域ならエラーになる筈です。)
左辺値参照は当然lvalueなので int& になるということのようです。

投稿2019/06/06 17:53

編集2019/06/07 07:23
Chironian

総合スコア23272

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

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

asobinin

2019/06/07 02:01

一見矛盾しているように見えて、実は矛盾していない。 矛盾しているように見えて、実は矛盾している。 矛盾って何でしたっけ?
alphya

2019/06/07 08:15

式 (a) の結果型はただの int です!
Chironian

2019/06/07 09:14

文法の定義というよりは、C++コンパイラの振る舞いをどのように理解するとバグを生みにくいか?という視点で書いています。 さて、(a)の型を int として取り扱うことが望ましいのであれば、何故にdecltype((a))は int& なのでしょうか? (a)を int& として扱った方が望ましいという規格策定者の意思を感じます。 (a)は「aを含む式」ですので「aに何らかの処理を施した結果」です。それがもしint型ならそれはaのコピーでないと他との整合性がとれません。なので、個人的には「(a)はaへの参照である」という理解を促すこのdecltypeの定義に納得できます。
yohhoy

2019/06/07 16:02 編集

(訂正:下記コメントは誤りです。ごめんなさい) <del>式 (a) それ自体の型は int& です(そうしないと `(a) = 123;` がill-formedになってしまう) 同様に式 a の型も int& ですが、decltype(a) は「変数 a の型を知りたい」ときに使うはずですから、むしろ括弧無しが特殊ケースという解釈かと思います。</del> ただし、Lvalue-to-rvalue変換によって int型のrvalue になることはあります(例えば `b = (a);`)
alphya

2019/06/07 09:53 編集

Chironian さん、yohhoy さん 式の型については、[expr.type]/1 にある > 式のタイプが「Tへの参照」である場合、そのタイプはそれ以降の分析の前にTに調整されます。 と、実際に cppreference の 値カテゴリのページに > またC++ の式 (演算子とその被演算子、リテラル、変数名など) は、それぞれ2つの独立した性質、型および値カテゴリによって特徴付けられます。 それぞれの式は何らかの非参照型を持ち、それぞれの式は以下のように定義される3つの基本的な値カテゴリ prvalue、 xvalue、 lvalue のいずれかひとつだけに属します。 との記述があったので、(a) の型は int と考えました。 個人的には、(a) = 123; と a = 123; は等価である、という解釈なのかと思います。 (そして、decltype 使用時だけ、括弧有を特殊ケースとみなすという解釈です。) この場合、(a) も a も int 型の lvalue となります。 また、yohhoy さんの回答にある英語ですが、"lvalueness" は参照を意味するとありますが、expression が参照を意味するとは明記されてなさそうだと思います。(これはあくまでも decltype の挙動を言っているのでしょうか...)
yohhoy

2019/06/07 09:58

alphya さん指摘の通り、 式 (a) も 式 a も「int型のlvalue」ですね。失礼しました。 > "lvalueness" は参照を意味するとありますが、expression が参照を意味するとは明記されてなさそうだと思います。(これはあくまでも decltype の挙動を言っているのでしょうか...) 括弧無しのid-expression(unparenthesized id-expression)を除いて、「decltype適用対象の式のlvaluenessは、decltype結果型が参照型になることを意味する」というニュアンスかと思います。
alphya

2019/06/07 10:04

yohhoy さん なるほど、そのように考えると辻褄が合いそうです! ありがとうございます!
Chironian

2019/06/07 11:24

あれ? std::vector<int> x; の x.at(0)は式と思います。そしてこの型は int& ではないでしょうか? これを int型として理解するのは現実的ではないように感じます。x.a(0)=123;ができることを説明できないですから。 「それぞれの式は何らかの非参照型を持ち」は、「それぞれの式は何らかの非参照型として処理され」くらいの意味ということはないでしょうか?(原文も is ではなく has なので参照型も持つことを否定するほどの強い表現ではないように感じます。https://en.cppreference.com/w/cpp/language/value_category)
alphya

2019/06/07 12:19

Chironian さん [expr.type]/1 をさらに引用すると、 > 式のタイプが「Tへの参照」である場合、そのタイプはそれ以降の分析の前にTに調整されます。 式は、参照によって示されるオブジェクトまたは関数を指定し、式は式に応じて左辺値またはx値です。 とあります。 完全に個人的な解釈ですが、よく「参照は既存のオブジェクトに別名をつける」という表現をされていることを考えると、規格は、 「参照を含む式は、あたかもその参照が参照先のオブジェクトであるかのように処理してください」 ということを言いたいのではないかと思われます。 ですので、例示されている x.at(0) は、結果的に int 型の lvalue 式として評価される、と考えられないでしょうか。(難しいです...)
alphya

2019/06/07 12:24

また、参照はオブジェクトではないので(cppreference 参照宣言 より)、「オブジェクトではないものへ代入することはできない」とも考えられないでしょうか
Chironian

2019/06/07 13:45

> 「参照を含む式は、あたかもその参照が参照先のオブジェクトであるかのように処理してください」 私もこの通りと思います。 ご提示されている記述は、恐らく式の結果が参照か否かについて言及しているのではなく、処理中に現れた参照をオブジェクトとして処理する旨が記載されているだけと思います。つまり、式の結果が参照になることを特に否定していないように思います。 つまり、「(a)の型はint&であるが、int型オブジェクトとして計算する」という記述に矛盾はないと考えています。
alphya

2019/06/07 14:18 編集

Chironian さん 返信ありがとうございます。 なるほど、その可能性もあるかもしれません... なにか、確かめる手段が欲しいところですね...m(> <*)m
yohhoy

2019/06/07 15:49 編集

少なくとも「(a)の型はint&であるが、int型オブジェクトとして計算する」は正しくないと思います。 C++11 [expr.prim.general]/p6では下記の通り、式 (a) の型は 式 a の型に等しいことが明言されています。 > A parenthesized expression is a primary expression whose type and value are identical to those of the enclosed expression. The presence of parentheses does not affect whether the expression is an lvalue. The parenthesized expression can be used in exactly the same contexts as those where the enclosed expression can be used, and with the same meaning, except as otherwise indicated. > std::vector<int> x; の x.at(0)は式と思います。そしてこの型は int& ではないでしょうか? については、alphya さんコメント C++11 [expr]/p5 にある通り「Tへの参照型はまずT型に調整される」ため、そもそも int&型 と int型 を区別する議論にさほど意味がないと考えています(≒"調整"の前後を観測することに意味がない)。もちろんvalue categoryはlvalueですから、いずれにせよ代入可能ではありますね。 > If an expression initially has the type “reference to T” (8.3.2, 8.5.3), the type is adjusted to T prior to any further analysis. The expression designates the object or function denoted by the reference, and the expression is an lvalue or an xvalue, depending on the expression.
yohhoy

2019/06/07 15:59 編集

C++11 [expr.call]/p3によれば「関数呼び出し式の型は静的に選ばれた関数の戻り値型に等しい」とあるので、std::vector<int> x; の 式 x.at(0) はstd::vector<int>::reference つまり int& ですね(Chironian さん解釈通り) > If the postfix-expression designates a destructor (12.4), the type of the function call expression is void; otherwise, the type of the function call expression is the return type of the statically chosen function (i.e., ignoring the virtual keyword), even if the type of the function actually called is different. This type shall be an object type, a reference type or the type void.
yohhoy

2019/06/07 16:08

ごちゃごちゃしてしまったのでまとめると、私の解釈では下記の通りです: int a; の 式 a は int 型 の lvalue。decltype(a) は int 型 int a; の 式 (a) は int 型 の lvalue。decltype((a)) は int& 型 std::vector<int> x; の 式 x.at(0) は int& 型 の lvalue。decltype(x.at(0)) は int& 型
Chironian

2019/06/07 19:18

> int a; の 式 (a) は int 型 の lvalue。decltype((a)) は int& 型 なるほど。(a)はint型と明確に定められているのですね。(a)はint&型と思ってました。 しかし、int型の(a)に対してdecltype((a))をint&型という定義は非常に不自然ですね。 (a)をint型と定めたけどTMP辺りでint&型とすれば解決する問題がでてきた。 今更(a)の型を変えられないのでdecltypeでTMPの問題に対処したとか。 ま、妄想です。
alphya

2019/06/08 01:16

yohhoy さん なるほど!そうだったのですか...! 解説、ありがとうございます!!
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.35%

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

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

質問する

関連した質問