元ネタ
if文の誤解 - Qiita
http://qiita.com/Ratty27/items/c142412bc6114e2f5c4c
という記事が先日Qiitaに投稿された。この記事には誤解がある。C++erに限定して話を進めよう。C++に限定しない誤解もあるがすでにコメントで指摘されているのでここでは触れない。
「if文が要求するのはbool型の値である」という誤解
例えば C++ なら、
if( 条件式 ) {}
というように考えていませんか? これは間違いで、実際には
if( bool ) {}
です。
ですので、if 文には true か false のどちらかを与える、というのが基本的な考え方です。
そして、if 文は引数としての bool 値が true の時に、{} 内の処理を実行する、という構文なのです
ですから、例えば
if( a == 0 ) {}
これは次のように書き換えることができます。
bool is_zero = (a == 0); if( is_zero ) {}
元記事によればif文が要求するのはbool型の値であり、条件式ではない、と主張しており、
if( a == 0 ) {}
bool is_zero = (a == 0);
if( is_zero ) {}
が等価だと主張している。ところがこれはそうではない。
C++11/C++14/C++17のif/switch/while/for文を解剖する
まずは規格書をあたって見る必要がありそうだ。ところでC++17でこの辺の文法に変化があったのでついでに触れておく。というのはcpprefjpのその辺の解説がまだ中途半端だからだ、私が書いていないから(ぇ。まあ変わったと言っても、if constexpr
文の導入とif/switch文への初期化文の導入だけなので大差はないのだが。
C++14まで
§ 6.4 stmt.select
selection-statement:
if
(
condition
)
statement
if
(
condition
)
statement
else
statement
switch
(
condition
)
statement
§ 6.5 stmt.iter
iteration-statement:
while
(
condition
) statement
do
statement
while
(
expression
) ;
for
(
for-init-statement condition
opt
;
expression
opt
)
statement
for
(
for-range-declaration
:
for-range-initializer
)
statement
C++17
§ 9.4 stmt.select
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
§ 9.5 stmt.iter
iteration-statement:
while
(
condition
) statement
do
statement
while
(
expression
) ;
for
(
init-statement condition
opt
;
expression
opt
)
statement
for
(
for-range-declaration
:
for-range-initializer
)
statement
condition
とは
どうも問題となっているのはcondition
のようだ。こいつは何者か?
C++14までとC++17で書き方は変わっているが言っていることは同じだ。
attribute-specifier-seq
opt
decl-specifier-seq declarator brace-or-equal-initializer
ってのは
if(FILE* fp = fopen("sample.txt", "rb")) {
//変数fpがこのスコープで有効
}
みたいなやつだ。
C++14まで
§ 6.4 stmt.select
condition:
expression
attribute-specifier-seq
opt
decl-specifier-seq declarator
=
initializer-clause
attribute-specifier-seq
opt
decl-specifier-seq declarator braced-init-list
C++17
§ 9 stmt.stmt
condition:
expression
attribute-specifier-seq
opt
decl-specifier-seq declarator brace-or-equal-initializer
§ 11.6 dcl.init
brace-or-equal-initializer:
=
initializer-clause
braced-init-list
特殊なcondition
= contextually converted to bool
condition
とはようはexpression
(式)ないし初期化宣言文だとわかったが、どんなexpression
や初期化宣言文でもいいかというとそういうわけでもない。switch
文以外のcondition
はcontextually converted to bool
(文脈上boolに変換される)という変換がなされる。
(~C++14)§ 6.4 stmt.select
(C++17)§ 9 stmt.stmt4 The value of a condition that is an initialized declaration in a statement other than a
switch
statement is the value of the declared variable contextually converted tobool
(Clause 7). If that conversion is ill-formed, the program is ill-formed. The value of a condition that is an initialized declaration in aswitch
statement is the value of the declared variable if it has integral or enumeration type, or of that variable implicitly converted to integral or enumeration type otherwise. The value of a condition that is an expression is the value of the expression, contextually converted tobool
for statements other thanswitch
; if that conversion is ill-formed, the program is ill-formed. The value of the condition will be referred to as simply “the condition” where the usage is unambiguous.
contextually converted to bool
という変換が合法かは
bool t(e) ;
という式が合法かどうかで判断される。
(~C++14)§ 4 conv
(C++17)§ 7 convCertain language constructs require that an expression be converted to a Boolean value. An expression
e
appearing in such a context is said to be contextually converted tobool
and is well-formed if and only if the declarationbool t(e);
is well-formed, for some invented temporary variablet
(11.6).
元記事の誤解
contextually converted to bool
さて、元記事の誤解が見えてきた。次のような例を見てみよう。
#include <iostream>
struct Hoge{
int n;
constexpr explicit operator bool() const { return 0 != this->n; }
};
int main()
{
constexpr Hoge hoge = {};
if (hoge){
std::cout << "is not zero" << std::endl;
}
else {
std::cout << "is zero" << std::endl;
}
}
元記事の主張が正しければ
#include <iostream>
struct Hoge{
int n;
constexpr explicit operator bool() const { return 0 != this->n; }
};
int main()
{
constexpr Hoge hoge = {};
bool b = (hoge == 0);
if (b){
std::cout << "is not zero" << std::endl;
}
else {
std::cout << "is zero" << std::endl;
}
}
こう書き換えてもいいはずだが、これはコンパイルエラーだ。bool
型変数への=初期化文は
contextually converted to bool
という変換が行われる文脈ではないからだ。
元記事の言いたかったことを表すコードは
#include <iostream>
struct Hoge{
int n;
constexpr explicit operator bool() const { return 0 != this->n; }
};
int main()
{
constexpr Hoge hoge = {};
bool b(hoge);
if (b){
std::cout << "is not zero" << std::endl;
}
else {
std::cout << "is zero" << std::endl;
}
}
こうあるべきだった。
そもそもexpression
以外もかける
まあcontextually converted to bool
という変換が起こったときに合法なexpression
という意味で
if( bool ) {}
と書くのはまだいいとして、そもそもわりとはじめに書いたように
if(FILE* fp = fopen("sample.txt", "rb")) {
//変数fpがこのスコープで有効
}
expression
以外も書ける。これを無視するのはどうなんだろうか。