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

# 元ネタ

>if文の誤解 - Qiita
>http://qiita.com/Ratty27/items/c142412bc6114e2f5c4c

という記事が先日Qiitaに投稿された。この記事には誤解がある。C++erに限定して話を進めよう。C++に限定しない誤解もあるがすでにコメントで指摘されているのでここでは触れない。

# 「if文が要求するのはbool型の値である」という誤解

>例えば C++ なら、
>
```cpp
  if( 条件式 ) {}
```

>というように考えていませんか？　これは間違いで、実際には

>
```cpp
  if( bool ) {}
```

>です。
>
>ですので、if 文には true か false のどちらかを与える、というのが基本的な考え方です。
>
>そして、if 文は引数としての bool 値が true の時に、{} 内の処理を実行する、という構文なのです
>
>---
>ですから、例えば
>
```cpp
if( a == 0 ) {}
```

>これは次のように書き換えることができます。

>
```cpp
bool is_zero = (a == 0);
if( is_zero ) {}
```

元記事によればif文が要求するのはbool型の値であり、条件式ではない、と主張しており、

```cpp
if( a == 0 ) {}
```

```cpp
bool is_zero = (a == 0);
if( is_zero ) {}
```

が等価だと主張している。ところがこれはそうではない。

# C++11/C++14/C++17のif/switch/while/for文を解剖する

まずは規格書をあたって見る必要がありそうだ。ところでC++17でこの辺の文法に変化があったのでついでに触れておく。というのはcpprefjpのその辺の解説がまだ中途半端だからだ、[私が書いていないから(ぇ](https://github.com/cpprefjp/site/issues/415)。まあ変わったと言っても、`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`*<sub>*`opt`*</sub>`; `*`expression`*<sub>*`opt`*</sub>`) `*`statement`*
>  - **`for`**` ( `*`for-range-declaration`*` : `*`for-range-initializer`*` ) `*`statement`*


## C++17

>§ 9.4 stmt.select
>
>- *`selection-statement:`*
>  - **`if constexpr`**<sub>*`opt`*</sub>` ( `*`init-statement`*<sub>*`opt`*</sub>` `*`condition`*` ) `*`statement`*
>  - **`if constexpr`**<sub>*`opt`*</sub>` ( `*`init-statement`*<sub>*`opt`*</sub>` `*`condition`*` ) `*`statement`*` `**`else`**` `*`statement`*
>  - **`switch`**` ( `*`init-statement`*<sub>*`opt`*</sub>` `*`condition`*` ) `*`statement`*
>
>§ 9.5 stmt.iter
>
>- *`iteration-statement:`*
>  - **`while`**` ( `*`condition`*` ) statement`
>  - **`do`***` statement `***`while`**` ( `*`expression`*` ) ;`
>  - **`for`**` ( `*`init-statement condition`*<sub>*`opt`*</sub>`; `*`expression`*<sub>*`opt`*</sub>`) `*`statement`*
>  - **`for`**` ( `*`for-range-declaration`*` : `*`for-range-initializer`*` ) `*`statement`*

# `condition`とは

どうも問題となっているのは`condition`のようだ。こいつは何者か？

C++14までとC++17で書き方は変わっているが言っていることは同じだ。

>*`attribute-specifier-seq`*<sub>*`opt`*</sub>*` decl-specifier-seq declarator brace-or-equal-initializer`*

ってのは

```cpp
if(FILE* fp = fopen("sample.txt", "rb")) {
  //変数fpがこのスコープで有効
}
```

みたいなやつだ。

## C++14まで

>§ 6.4 stmt.select
>
>- *`condition:`*
>  - *`expression`*
>  - *`attribute-specifier-seq`*<sub>*`opt`*</sub>*` ``decl-specifier-seq declarator `*`=`*` initializer-clause`*
>  - *`attribute-specifier-seq`*<sub>*`opt`*</sub>*` ``decl-specifier-seq declarator braced-init-list`*

## C++17

>§ 9 stmt.stmt
>
>- *`condition:`*
>  - *`expression`*
>  - *`attribute-specifier-seq`*<sub>*`opt`*</sub>*` 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.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`という変換が合法かは

```cpp
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`
さて、元記事の誤解が見えてきた。次のような例を見てみよう。

```cpp
#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;
    }
}
```

https://wandbox.org/permlink/WkKoIWNj2YSm03Iv

元記事の主張が正しければ

```cpp
#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;
    }
}
```

https://wandbox.org/permlink/cP2Ll1e7rh7SJCNe

こう書き換えてもいいはずだが、これはコンパイルエラーだ。`bool`型変数への=初期化文は
`contextually converted to bool`という変換が行われる文脈ではないからだ。

元記事の言いたかったことを表すコードは

```cpp
#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;
    }
}
```

https://wandbox.org/permlink/HTpOZMzZweYZlQYo

こうあるべきだった。

## そもそも`expression`以外もかける

まあ`contextually converted to bool`という変換が起こったときに合法な`expression`という意味で

>
```cpp
if( bool ) {}
```

と書くのはまだいいとして、そもそもわりとはじめに書いたように

```cpp
if(FILE* fp = fopen("sample.txt", "rb")) {
  //変数fpがこのスコープで有効
}
```

`expression`以外も書ける。これを無視するのはどうなんだろうか。

# 参考

- [本の虫: C++の規格におけるcontextually converted to boolという用語について](
https://cpplover.blogspot.jp/2015/09/ccontextually-converted-to-bool.html)

# License

[CC BY 4.0](https://creativecommons.org/licenses/by/4.0/deed.ja)

<a href="https://commons.wikimedia.org/wiki/File:CC-BY_icon.svg#/media/File:CC-BY_icon.svg"><img src="https://upload.wikimedia.org/wikipedia/commons/thumb/1/16/CC-BY_icon.svg/1200px-CC-BY_icon.svg.png" alt="CC-BY icon.svg" width="88px"></a>