constexprおじさん

# constおじさんはconstexprおじさんに進化した！

「constおじさん」という記事を書いて5年経過したらしい。

https://qiita.com/ktetsuo/items/9edc244f544ffedf0b28

constおじさんの私はconstexprを知りconstexprを積極的に使うようになった。

# constexprとは

constant expression(定数式)の略らしい。
どう発音するか毎回迷う。コンストエクスプル？と心の中で言っていたが、マイナーらしい。 →[constexprの読み方 - Qiita](https://qiita.com/tags/%e8%aa%ad%e3%81%bf%e6%96%b9)

constexprはC++11で導入された。コンパイル時に値が決定する定数のこと。
「定数定義には`#define`よりも`const`を使おう」というのはよく言われていたが、定数定義は`constexpr`で置き換えることができる。
`const`は「実行時にしか値が決まらないがその後は変更されない変数」に使うのがよさそう。

# constexprの良いところ

下のコードを見てほしい。
100円に税金10%をつける簡単なコード。

```c++:main.cpp
#include <iostream>

#define DEFINE_YEN (100)
#define DEFINE_TAX (0.1F)
#define DEFINE_YEN_TAX (DEFINE_YEN + DEFINE_YEN * DEFINE_TAX)

const int CONST_YEN = 100;
const float CONST_TAX = 0.1F;
const float CONST_YEN_TAX = CONST_YEN + CONST_YEN * CONST_TAX;

constexpr int CONSTEXPR_YEN = 100;
constexpr float CONSTEXPR_TAX = 0.1F;
constexpr float CONSTEXPR_YEN_TAX = CONSTEXPR_YEN + CONSTEXPR_YEN * CONSTEXPR_TAX;

int main()
{
	std::cout << "DEFINE_YEN        " << DEFINE_YEN << std::endl;
	std::cout << "DEFINE_TAX        " << DEFINE_TAX << std::endl;
	std::cout << "DEFINE_YEN_TAX    " << DEFINE_YEN_TAX << std::endl;
	std::cout << "CONST_YEN         " << CONST_YEN << std::endl;
	std::cout << "CONST_TAX         " << CONST_TAX << std::endl;
	std::cout << "CONST_YEN_TAX     " << CONST_YEN_TAX << std::endl;
	std::cout << "CONSTEXPR_YEN     " << CONSTEXPR_YEN << std::endl;
	std::cout << "CONSTEXPR_TAX     " << CONSTEXPR_TAX << std::endl;
	std::cout << "CONSTEXPR_YEN_TAX " << CONSTEXPR_YEN_TAX << std::endl;
	return 0;
}
```

## 括弧を付けなくてもよい

`define`は展開されるだけなので括弧`()`を付けておかないと危険な場合がある。
`constexpr`や`const`は変数定義なので、気にする必要がない。

## スコープを指定できる

`define`を書くとそのソースコード内すべてで有効になってしまうため、スコープが指定できない。
`constexpr`や`const`はnamespaceの中に入れたり、ローカル変数にすることができる。

## 型がつけられる

`constexpr`や`const`は`define`と違い型がつけられる（付けなければならない）。
したがって、型が間違っているとコンパイル時に検出できる。
例えば上記例の`CONSTEXPR_YEN_TAX`を`int`としてしまうと警告が出る。

## (Qiita上で)シンタックスハイライトが効く

上のコードを見て一目瞭然。
※VisualStudioは`define`の方を色付けしてくる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70022/301cfb44-425e-4c59-861f-1ca679b5f6d1.png)


## Visual Studioで値が見える

定義にマウスオーバーすると、`define`は展開された結果が見えるだけ。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70022/bba53f2d-cb4b-9859-9a5e-5f1fad2c38e4.png)

`const`は型が表示されるだけ。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70022/8aff3378-a135-8fc8-4de9-bac5e634ccab.png)

`constexpr`は計算結果が見える。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70022/ac15b4d3-1cda-2b2d-77b2-bb7b2b5308bb.png)

## static_assertが使える

`define`と`constexpr`はコンパイル時に値が決まるので、static_assertが使え、コンパイル時に不正な値を検出できる。

```c++
	static_assert(DEFINE_TAX    < 0.5, "Tax is too high!");
	static_assert(CONSTEXPR_TAX < 0.5, "Tax is too high!");
	static_assert(CONST_TAX     < 0.5, "Tax is too high!");	// NG
	assert(CONST_TAX < 0.5);	// OK
```

`const`は実行時にしか値が決まらないので、`<cassert>`をincludeして`assert`を使うしかない。エラーは実行時にしか出ない。

## constexprな関数が作れる

下記のように、`constexpr`を関数定義につけることで計算をコンパイル時に行うことができるので、配列の個数などにも使用できる。
`const`ではコンパイル時に計算されないため、配列の個数には使えない。

```c++:main.cpp
#include <stdint.h>

#define DEFINE_CALC_SIZE(count) (sizeof(int) * (count))

const size_t const_calc_size(size_t count)
{
	return sizeof(int) * count;
}

constexpr size_t constexpr_calc_size(size_t count)
{
	return sizeof(int) * count;
}

int main()
{
	uint8_t buf1[DEFINE_CALC_SIZE(10)];
	uint8_t buf2[const_calc_size(10)];	// NG
	uint8_t buf3[constexpr_calc_size(10)];	// OK
	return 0;
}
```

`define`でマクロを定義するよりも複雑なことができる。たとえば以下のようにループを書くこともできる。(C++14以降)

```c++:main.cpp
#include <stdint.h>

constexpr size_t constexpr_calc_size2(size_t n)
{
	size_t size = 0;
	for (size_t i = 0; i < n; i++) {
		size += i;
	}
	return size;
}

int main()
{
	constexpr size_t SIZE = constexpr_calc_size2(100);
	uint8_t buf4[SIZE];
	return 0;
}
```

ちゃんとコンパイル時に計算されるため、SIZEにマウスオーバーすると値が表示される。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70022/91f40824-1273-d93f-3173-9c5c6dbd4652.png)


constexpr関数にしても、constexprでない通常の呼び方は可能。この場合、計算は実行時になるため、constexprの変数に代入したり配列の個数に設定することはできない。

```c++:main.cpp
constexpr size_t constexpr_calc_size(size_t count)
{
	return sizeof(int) * count;
}

int main()
{
	size_t count = 12;
	const size_t bufsize = constexpr_calc_size(count);	// OK 実行時に計算される constへの代入はOK
	constexpr size_t bufsize = constexpr_calc_size(count);	// NG
	return 0;
}
```

## #ifの代わりにif constexprが使える(C++17以降)

定数を判定するコンパイルスイッチを`#if`で行うことがあるが、定数の判定なら`if constexpr`が使える。

```c++:main.cpp
#include <iostream>

constexpr int A = 0;
#define B (0)

int main()
{
	if constexpr (A == 0) {
		std::cout << "A is 0" << std::endl;
	} else {
		std::cout << "A is not 0" << std::endl;
		//ここは文法解釈される
	}
#if B == 0
	std::cout << "B is 0" << std::endl;
#elif
	std::cout << "B is not 0" << std::endl;
	ここは文法解釈されないのでコンパイルエラーにならない
#endif
}
```

コメントに書いたように、`#if`で条件から外れた部分は文法解釈もされないので、その部分が文法的に間違っていてもコンパイルエラーにならないが、`if constexpr`で条件を外れた部分は文法解釈されるので間違っているとコンパイルエラーになる。
コンパイルスイッチを切り替えたらコンパイルが通らなかったといった事故を防げる。

ちなみに、`if constexpr`で条件から外れた部分は文法解釈はされるが実体のコードにはならない模様。安心して使えそう。

```text:逆アセンブル結果
int main()
{
00007FF7A41F2410  push        rbp  
00007FF7A41F2412  push        rdi  
00007FF7A41F2413  sub         rsp,0E8h  
00007FF7A41F241A  lea         rbp,[rsp+20h]  
00007FF7A41F241F  lea         rcx,[__34B43460_Constexpr3@cpp (07FF7A4203066h)]  
00007FF7A41F2426  call        __CheckForDebuggerJustMyCode (07FF7A41F13E3h)  
	if constexpr (A == 0) {
		std::cout << "A is 0" << std::endl;
00007FF7A41F242B  lea         rdx,[string "A is 0" (07FF7A41FAC10h)]  
00007FF7A41F2432  mov         rcx,qword ptr [__imp_std::cout (07FF7A4201198h)]  
00007FF7A41F2439  call        std::operator<<<std::char_traits<char> > (07FF7A41F108Ch)  
00007FF7A41F243E  lea         rdx,[std::endl<char,std::char_traits<char> > (07FF7A41F103Ch)]  
00007FF7A41F2445  mov         rcx,rax  
00007FF7A41F2448  call        qword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (07FF7A42011B0h)]  
	} else {
		std::cout << "A is not 0" << std::endl;  ★★この部分のアセンブリコードは生成されていない！
		//ここは文法解釈される
	}
#if B == 0
	std::cout << "B is 0" << std::endl;
00007FF7A41F244E  lea         rdx,[string "B is 0" (07FF7A41FAC18h)]  
00007FF7A41F2455  mov         rcx,qword ptr [__imp_std::cout (07FF7A4201198h)]  
00007FF7A41F245C  call        std::operator<<<std::char_traits<char> > (07FF7A41F108Ch)  
00007FF7A41F2461  lea         rdx,[std::endl<char,std::char_traits<char> > (07FF7A41F103Ch)]  
00007FF7A41F2468  mov         rcx,rax  
00007FF7A41F246B  call        qword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (07FF7A42011B0h)]  
#elif
	std::cout << "B is not 0" << std::endl;
	ここは文法解釈されないのでコンパイルエラーにならない
#endif
}
00007FF7A41F2471  xor         eax,eax  
00007FF7A41F2473  lea         rsp,[rbp+0C8h]  
00007FF7A41F247A  pop         rdi  
00007FF7A41F247B  pop         rbp  
00007FF7A41F247C  ret 
```


# 最後に

constexprはコンパイル時に計算されるので、実行時には全くコストがかかりません。
あなたもconstexprが使いたくなりませんか？