今回はC++規格書のif
文周りを読んでみたい。
今更一体全体何なんだと思う人が大半だと思うが、普段息をするように使っているものだからこそ、規格書を読んでおくのは良いことだと思うのだ。普通の人が規格書を読むのは、知らないものについて勉強するときか、既に知っているものに新機能が追加されたときだろう。初期に勉強した機能ほど、厳密な規格を知らずに使っている気がする。そしてそういう基本的な機能ほど当たり前だがよく使うので影響範囲は広い。となると、規格に当たらずに使っているのがだんだん怖くなってこないだろうか。
というわけで読もう。11, 14, 17の変遷も追いたい。
C++11
まずはC++11(策定直後のドラフト N3337)から見てみる。if
についての記述があるのは、§6.4 "Selection statements"だ。
ここでは、if
とswitch
のような、複数の制御フローの中から一つだけを選択するための文について書かれている。以下のような形になるらしい。
selection-statement: if ( condition ) statement if ( condition ) statement else statement switch ( condition ) statement condition: expression attribute-specifier-seq_opt decl-specifier-seq declarator = initializer-clause attribute-specifier-seq_opt decl-specifier-seq declarator braced-init-list
とりあえず、今回はif
に集中したいのでswitch
は無視する。さらに、if
に続くことのできるstatement
の詳細は§6全体で説明されている「文」であり、これもどんなものがあるか一つ一つ見ていったりすると長くなるのでスキップしたい。
if
の次に続く文は暗黙にブロックスコープを導入すると書かれている。つまり、
if (x) int i;
は、
if (x) { int i; }
と等価であり、i
はif
を抜けるとスコープを抜けてしまうことが書かれている。ブロックを明示的に書こうが書くまいが、if
に続く変数の寿命はif
の外では尽きる。せやな。
さて、condition
は以下のように定義されている。
condition: expression attribute-specifier-seq_opt decl-specifier-seq declarator = initializer-clause attribute-specifier-seq_opt decl-specifier-seq declarator braced-init-list
expression
はよい。これはbool
として評価できるべき式であり、みなさんが普段使っている形式だ。だが他の2つは比較的使われていないというか、知られていないかも知れない。
attribute-specifier-seq
は属性構文というやつで、[[nodiscard]]
とかそういうやつだ。alignas
もこれに含まれるらしい(§7.6.1)。_opt
は省略可の意。
decl-specifier-seq
はdecl-specifier
の列で、以下のようになっている(§7.1)。
decl-specifier: storage-class-specifier type-specifier function-specifier friend typedef constexpr decl-specifier-seq: decl-specifier attribute-specifier-seq opt decl-specifier decl-specifier-seq
順に見てみよう。storage-class-specifier
は、register
, static
, thread_local
, extern
, mutable
のどれかを指す。register
なんてあったなそういえば。register
は値が何度も何度も使われるから効率の良い場所に置いてね、というヒントを与えるためのもので、処理系があまりそういうことが上手でなかった古の時代には意味があったが、現代ではほぼ全てのコンパイラが「言われんでもわかっとるわ」とばかりにガン無視を決め込むキーワードである。覚えなくていい。C++11時点でdeprecatedであり、将来的に全く異なる機能で再利用する方向で議論されている(auto
のように)。
type-specifier
は……長くなる。詳細は§7.1.6を見て欲しいのだが、まあ要するに型名のことだと思ってくれていい。
function-specifier
はinline
、virtual
、explicit
のどれかだ。関数の前につくやつ。「そうですね」って感じ。
ほか、friend
、typedef
、constexpr
はもういいだろう。そのままなので。
というわけで、decl-specifier
は何かのdeclarationの時に出てくるやつということがわかる。
condition: expression attribute-specifier-seq_opt decl-specifier-seq declarator = initializer-clause attribute-specifier-seq_opt decl-specifier-seq declarator braced-init-list
declarator = initializer-clause
とかdeclarator braced-init-list
はもうわかるだろう。x = 1
とかx{1, 2, 3}
とかのことだ。つまり、if(condition)
のcondition
では変数宣言ができる。ただし、それはbool
に変換可能である必要があり、そうでなければそのプログラムは不適格となる。
ちなみにここで導入された変数はそのif
直後の(暗黙かどうかを問わない)ブロックスコープにおいて生存する。ちなみに、そのif
の後のelse
に続くスコープも同様である。わかりやすい例が規格書に乗っているので引用しておこう。
if (int x = f()) { int x; // 不適格、xを再定義している } else { int x; // 不適格、xを再定義している }
これはあまり知られていないっぽいくせに結構便利な機能で、例えば以下のようなことができる。
boost::optional<data> get_data(); if(auto data = get_data()) { // データ処理 }
とか、
boost::expected<data, error> parse(); if(auto parsed = parse()) { // データ処理 } else { // エラー処理 }
などだ。モダンな言語でよく採用されている、エラー処理を直和型を使って明示的にやっていく方式を採用するなら、この構文はかなり役に立つと思う。
boost::optional
は使い方の説明で当たり前のようにこの構文を使っている。既にこの機能を知っているという人はboost::optional
のドキュメントで知ったとかではないだろうか。他に紹介している文献を寡聞にして見たことがない。ちなみに私はboost::optional
経由で知った。
C++14
ではC++14で何か変わったか調べるために、規格策定直後のドラフト、N4140を見てみよう。構成があまり変わっていないので、同じ§6.4にある。読んでみよう。
変化、なし――
まあ、C++14はマイナーチェンジだったから仕方ないね。
C++17
C++17では結構大きめの変更が入っている。規格書の構成も結構変わって、該当する箇所が§9.4になった。
selection-statement: if constexpr_opt ( init-statement_opt condition ) statement if constexpr_opt ( init-statement_opt condition ) statement else statement switch ( init-statement_opt condition ) statement
if constexpr
も同じ所で触れるんだな。まあこれに関しては日本語でも結構解説があるので一旦飛ばすことにしよう。普通のif
に注目する。
init-statement
はあってもなくても構わないが、ある場合はセミコロンで続くcondition
と区切られる。このinit-statement
はその名の通り何かの変数をその場で定義できるというもので、以下のような使い方ができる。
if(auto x = calc(); x > 0.0) { // ... } else { // ... }
ではこの初期化式で導入された変数の生存期間はどうなるかというと、§9.4.1に以下のような記述がある。
if constexpr_opt (init-statement condition) statement
は、以下のコードと等価である。
{
init-statement
if constexpr_opt (condition) statement
}
というわけで、if
全体をブロックスコープで覆ったと思えばよい。if
のスコープを離れた時点で寿命は突き、デストラクタが呼ばれる。
constexpr if
の話をしなかったら割とあっさり終わってしまった。
まとめ
たかがif
、と思っていても、実はあまり見る機会のない構文が使えることがある。逆にif
のような最初期に学ぶであろう機能こそ、そういった知られざる便利機能が隠れていたりするのではないか。
規格書にあたるのは大事ですね。