LoginSignup
25
12

More than 5 years have passed since last update.

「if文の誤解」のC++における誤解

Last updated at Posted at 2017-07-15

元ネタ

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)statementelsestatement
    • switch(condition)statement

§ 6.5 stmt.iter

  • iteration-statement:
    • while(condition) statement
    • dostatementwhile(expression) ;
    • for(for-init-statement conditionopt;expressionopt)statement
    • for(for-range-declaration:for-range-initializer)statement

C++17

§ 9.4 stmt.select

  • selection-statement:
    • if constexpropt(init-statementoptcondition)statement
    • if constexpropt(init-statementoptcondition)statementelsestatement
    • switch(init-statementoptcondition)statement

§ 9.5 stmt.iter

  • iteration-statement:
    • while(condition) statement
    • dostatementwhile(expression) ;
    • for(init-statement conditionopt;expressionopt)statement
    • for(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:
    • expression
    • attribute-specifier-seqoptdecl-specifier-seq declarator=initializer-clause
    • attribute-specifier-seqoptdecl-specifier-seq declarator braced-init-list

C++17

§ 9 stmt.stmt

  • condition:
    • expression
    • attribute-specifier-seqoptdecl-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文以外のconditioncontextually converted to bool(文脈上boolに変換される)という変換がなされる。

(~C++14)§ 6.4 stmt.select
(C++17)§ 9 stmt.stmt

4 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 to bool (Clause 7). If that conversion is ill-formed, the program is ill-formed. The value of a condition that is an initialized declaration in a switch 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 to bool for statements other than switch; 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 conv

Certain 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 to bool and is well-formed if and only if the declaration bool t(e); is well-formed, for some invented temporary variable t (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以外も書ける。これを無視するのはどうなんだろうか。

参考

License

CC BY 4.0

CC-BY icon.svg

25
12
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
25
12