2009-08-20

decltypeの二重括弧が参照になる理由

こんにちは。みなさんのような優秀なプログラマなら、C++0xコードの脳内コンパイルぐらい普通に行っていると思うのですが、何しろC++0xの型システムは厄介です。そこで今回は、皆さんの脳の規格準拠度を調べてみることにしましょう。

C++0xにはdecltypeがあります。delctypeも知らないようでは、もはやC++0xプログラマとしては恥ずかしくて人前を歩けません。ところが、decltypeは悉達多プログラマには、少々難しいのです。

以下のコード辺を脳内コンパイルしたとき、皆さんの脳内には、どのような型情報が構築されているでしょうか

int e ; #1

decltype(e) ; // #2
decltype((e)) ; // #3

まず、#1で、eの型は、当然intです。次のコードが脳内コンパイル可能かどうかは、皆さんの脳内コンパイラのバージョンによっても異なるのですが、今時C++0x対応でないコンパイラを脳内にインストールしている人はまずいないでしょう。#2のdelctypeの結果の型は、intになります。では、#3はどうでしょう。じつは、int &なのです。従って、正解は以下の通りになります。

int e ; eの型はint

decltype(e) ; // 文全体のexpressionの型はint
decltype((e)) ; // 文全体のexpressionの型はint &

「なんで括弧で囲っただけで結果が変わるんだ。そんなの全然C++らしくないじゃないか。lispとかいう月の言語ならともかく」と、お思いになる方もいらっしゃるかもしれません。しかし、上記の挙動は正しい物です。

上記のコードを正しく脳内コンパイル出来た方は、もうこれ以上、ここに有益な情報はありません。お帰り下さい。ただし、「自分の脳内コンパイラがエラーを出したから、上記コードは間違っている」というエラーを返す、時代後れな脳内コンパイラをご使用の方は、どうか続きをお読みになって、脳内コンパイラをアップデートしてください。

まず、decltypeの仕様です。7.1.6.2のp4に書かれている通り。

The type denoted by decltype(e) is defined as follows:
— if e is an unparenthesized id-expression or a class member access (5.2.5), decltype(e) is the type of the entity named by e. If there is no such entity, or if e names a set of overloaded functions, the program is ill-formed;
— otherwise, if e is a function call (5.2.2) or an invocation of an overloaded operator (parentheses around e are ignored), decltype(e) is the return type of the statically chosen function;
otherwise, if e is an lvalue, decltype(e) is T&, where T is the type of e;
— otherwise, decltype(e) is the type of e.

ごらんのように、"unparenthesized id-expression"の場合はそれ自体の型、それ以外の場合で、lvalueならば、参照型と書いてありますね。実は、無括弧(unparenthesized)という文面は、この続きの文章を読む必要の無かった、最新の脳内コンパイラをインストールしている方には、必要のない言葉なのです。もし、この「無括弧」という単語が無かったとしても、上記コードの挙動は変わりません。この言葉は論理的な思考の出来ないド低脳、もとい脳内にバグのある方にとっても、意味を明確にするために、特別に書いてあるに過ぎないのです。

上記コードに必要な部分だけ抜き出すと、「eがid-expressionの場合は、e自体の型。それ以外の場合で、eがlvalueならば、参照型」と書いてあります。上記コードでは、クラスメンバーアクセスだの関数呼び出しだのといったことは行われていないので、この場合、気にする必要はありません。id-expressionとは、この場合、変数名という識別子です。上記コードのdecltype(e)では、一番最初の条件に合致するので、型はintになります。

さて、primary-expressionについて思い出してください。忘れてしまったバギーな方の為に、これも規格を引用しておきます。5.1.1に書かれている通り、

primary-expression:
  literal
  this
  ( expression )
  id-expression
  lambda-expression

さて、これをみれば一目瞭然で、id-expressionと、括弧でくくったexpressionは、正しい脳内コンパイラでは、別物として扱われているのです。正しい脳内コンパイラをお使いであれば、当然この辺は正しく解釈します。するべきです。しなければなりません。

さて、もう一度decltypeの決まり事を考えてみましょう。「eがid-expressionの場合は、e自体の型。それ以外の場合で、eがlvalueならば、参照型」です。eはid-expressionです。しかし、(e)は、もはやid-expressionではないのです。id-expressionを包括する別のexpressionです。ただ、id-expressionも包括できるというだけに過ぎません。id-expressionを括弧でくくった結果は、もはやid-expressionでは無くなります。従って、それ以外の場合になります。さて、(e)はlvalueでしょうか。これは疑う余地なくlvalueです。従って、「それ以外の場合で、eがlvalueならば、参照型」という条件に合致します。よって、decltype((e))はint &となります。

この続きを読む必要の無かった、普通のC++0xプログラマは、こうした型システムの計算を、脳内で瞬時に正しく行っているわけです。さて、皆さんの脳内コンパイラはdecltypeに対応しましたか。

追記:言語的には、論理的なのかも知れないけど、人間的には論理的には思えないんだよね。

  1. (expression)のexpressionを対して、
  2. (expression)という形式で無くなるまで1. を実行する。
  3. もし、expressionがid-expressionであった場合、decltypeではid-expressionとして扱う

というような仕組みがあってもいいんじゃないかな、とは思った。

4 comments:

zak said...

漸くdecltype((e))が腑に落ちました。(≒正しくアップデートされたようです)
ありがとうございました。

>悉達多プログラマ
思わずググってしまいました。

hito said...

悉達多プログラマという言い方は、例のconceptに関するStroustrupのインタビューの中で使われている言葉です。
http://www.devx.com/cplus/Article/42448/0/page/4

>DK: Let's talk about the future. (...) or has the time come to shelve concepts for now and focus on features that Joe and Siddhartha Coders would like to see in C++?

未だ悟りを得ていない未熟なプログラマ程度の意味合いです。
悉達多とは釈迦の俗世の時の名前です。悉達多は悟りを得た後に、旧友に会って、
「おーい、ゴータマ君」などと声をかけられた時に、「私はすでに悟りを得たのだから、ブッダ(悟りを得た者)と呼べ」などと、自ら言ってましたからね。

zak said...

そもそも「悉達多」が読めなかったというお話です・・・。

一応調査結果を貼っておきます。
http://dic.yahoo.co.jp/dsearch?enc=UTF-8&p=%E6%82%89&dtype=0&stype=2&dname=0ss&pagenum=11&index=108575100000
"悟りを開く前"だから"未熟な/成長途中の"という方がピンときますが、日本語辞書では逆の意味というところが面白い。

hito said...

シッダルタ自体の言葉の意味は間違っていないでしょう。
仮にも王子ですからね。変な名前を付けるわけがない。
シッダルタが未熟者の意味で使われているのは、同名の有名な旧教開祖がいた故事に基づいてでしょう。