元ネタ
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)statementif(condition)statementelsestatementswitch(condition)statement§ 6.5 stmt.iter
iteration-statement:while(condition) statementdostatementwhile(expression) ;for(for-init-statement conditionopt;expressionopt)statementfor(for-range-declaration:for-range-initializer)statement
C++17
§ 9.4 stmt.select
selection-statement:if constexpropt(init-statementoptcondition)statementif constexpropt(init-statementoptcondition)statementelsestatementswitch(init-statementoptcondition)statement§ 9.5 stmt.iter
iteration-statement:while(condition) statementdostatementwhile(expression) ;for(init-statement conditionopt;expressionopt)statementfor(for-range-declaration:for-range-initializer)statement
conditionとは
どうも問題となっているのはconditionのようだ。こいつは何者か?
C++14までとC++17で書き方は変わっているが言っていることは同じだ。
attribute-specifier-seqoptdecl-specifier-seq declarator brace-or-equal-initializer
ってのは
if(FILE* fp = fopen("sample.txt", "rb")) {
//変数fpがこのスコープで有効
}
みたいなやつだ。
C++14まで
§ 6.4 stmt.select
condition:expressionattribute-specifier-seqopt``decl-specifier-seq declarator=initializer-clauseattribute-specifier-seqopt``decl-specifier-seq declarator braced-init-list
C++17
§ 9 stmt.stmt
condition:expressionattribute-specifier-seqoptdecl-specifier-seq declarator brace-or-equal-initializer§ 11.6 dcl.init
brace-or-equal-initializer:=initializer-clausebraced-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
switchstatement 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 aswitchstatement 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 toboolfor 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
eappearing in such a context is said to be contextually converted tobooland 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以外も書ける。これを無視するのはどうなんだろうか。