C++、constexprのまとめ

# はじめに

C++には`constexpr`という概念がある。
これまでよくわかっていなかったのだが、きちんと調べて`constexpr`を理解したつもりになったので、ここにまとめる。

（以下の話は、全てC++17以降を想定している。）

# 話の要点

## `constexpr`を使えない・使うべきでない主な場面

### 変数

+ `const`でない変数
+ クラスのメンバ変数
+ 標準入力などの非`constexpr`関数を用いて計算する値
+ 引数などの`constexpr`でない可能性がある値を用いて計算する値

### 関数

+ `inline`化できない関数
+ 引数でも`this`でもない非`constexpr`な外側の変数を参照する操作を含む関数
+ 引数でも`this`でもない外側に副作用を及ぼすような操作を含む関数

## `constexpr`を使うべき主な場面

+ 上記以外全て


# 変数の`constexpr`

変数における`constexpr`は、`#define`などで作っていたようなコンパイル時定数を作るためのキーワードである。

```C++
#include <iostream>

// 標準入力から int 型の値を受け取って返す、（コンパイル時に値が定まらない）関数
auto get_value_from_stdin()
{
    int v;
    std::cin >> v;
    return v;
}

int main(){
    auto a = 1; // 普通の変数
    const auto b = 2; // Const
    constexpr auto c = 3; // Constexpr

    a = 4; // OK、 a は後から書き換えてよい
    // b = 5; // NG、 b は const なので書き換えてはいけない
    // c = 6; // NG、 c は const なので書き換えてはいけない
    std::cout << a << ", " << b << ", " << c << std::endl;

    auto d = get_value_from_stdin(); // OK、 d は実行時に受け取った値
    const auto e = get_value_from_stdin(); // OK、 e は実行時に受け取り、今後変更されない値
    // constexpr auto f = get_value_from_stdin(); // NG、 f はコンパイル時に値が確定しなければならない
    std::cout << d << ", " << e << /* ", " << f << */ std::endl;
    
    return 0;
}
```

`const`が「この変数は今後変更しませんし、変更しようとしたらコンパイルエラーにしてくださいね」という合図であるのに対し、変数に付ける`constexpr`は「この変数の値はコンパイル時に確定するし、確定しなければコンパイルエラーにしてくださいね」という合図である。
`constexpr`変数は`const`変数としても扱われる。

コンパイラは、`constexpr`がついた変数の値をコンパイル時に計算しようとする。もし計算できなければ、コンパイルエラーを吐く。
上のコードでは、「標準入力から受け取る」という操作がコンパイル時に行えないため、エラーとなっている。

では、どの操作が「コンパイル時に計算できる」のだろうか？
後述する通り、それは関数に`constexpr`キーワードがついているかどうかで判断されるのである。

# 関数の`constexpr`

## `constexpr`変数への返り値の代入

`constexpr`は関数にも付けることができる。
関数につけた`constexpr`キーワードは、「この関数はコンパイル時に計算することもできますよ」ということを表している。

```C++
#include <iostream>

// 常に 42 を返す関数
auto answer() { return 42; }

// 常に 42 を返す、 constexpr な関数
constexpr auto answer_constexpr() { return 42; }

// 常に 42 を返す、 constexpr な関数?
constexpr auto answer_print()
{
    // std::cout << 42 << std::endl; // NG、 標準出力への書き出しはコンパイル時には行えない
    return 42;
}

int main(){
    // constexpr auto a = answer(); // NG、 answer 関数は constexpr 関数ではない
    constexpr auto b = answer_constexpr(); // OK、 answer_constexpr 関数は constexpr 関数
    // constexpr auto c = answer_stdin(); // NG、 answer_stdin 関数はコンパイルできない
    std::cout << /* a << ", " << */ b << /* ", " << c << */ std::endl;
    
    return 0;
}
```

`constexpr`変数に入れられる値は、コンパイル時に計算できる値だけである。
だから、`constexpr`キーワードのない関数の返り値を`constexpr`変数に入れようとすると、コンパイラは「この値はコンパイル時には計算できない」と考え、`constexpr`変数には入れられないよと怒ってくれる。
また、`constexpr`キーワードがある関数の返り値については、関数の中身を見て値を計算しようとし、その過程で同様に「コンパイル時には計算できない」式がひとつでもあれば、やはり`constexpr`変数には入れられないよと怒ってくれる。

また、コンパイル時に計算されているかどうか確認したければ、`static_assert`を用いるという手もある。

```C++
// 常に 42 を返す関数
auto answer() { return 42; }

// 常に 42 を返す、 constexpr な関数
constexpr auto answer_constexpr() { return 42; }

int main(){
    // static_assert(answer() || true); // NG、 answer() はコンパイル時に計算できない
    static_assert(answer_constexpr() || true);
    
    return 0;
}
```

## `constexpr`ではない引数を与える

`constexpr`関数の結果は、常にコンパイル時に計算されるというわけではない。

```C++
#include <iostream>

// 標準入力から int 型の値を受け取って返す関数
auto get_value_from_stdin()
{
    int v;
    std::cin >> v;
    return v;
}

// 常に 42 を返す、 constexpr な関数
constexpr auto answer_constexpr([[maybe_unused]] int question)
{
    return 42;
}

int main(){
    constexpr auto value_constexpr = 1; // Constexpr な値
    const auto value_const = get_value_from_stdin(); // Const だが constexpr ではない値
    
    constexpr auto a = answer_constexpr(value_constexpr); // OK、引数が constexpr なので、コンパイル時に値が確定する
    // constexpr auto b = answer_constexpr(value_const); // NG、引数が constexpr な値ではないので、コンパイル時に値が確定せず、コンパイルエラー
    const auto c = answer_constexpr(value_constexpr); // OK、引数が constexpr なので、コンパイル時に値が確定する
    const auto d = answer_constexpr(value_const); // (1) OK、引数が constexpr な値ではないので、コンパイル時に値は確定しないが、コンパイルエラーにはならない

    std::cout << a << ", " << /* b << ", " << */ c << ", " << d << std::endl;
    
    return 0;
}
```
この例では、`constexpr`関数の引数`value2`が`constexpr`ではない。
この場合でもコンパイルは成功し、`answer_constexpr`はあたかも`constexpr`キーワードのない関数かのように（少なくとも(1)の行では）振る舞う。

`constexpr`キーワードはあくまで「コンパイル時に値が確定できる」ことを伝えるだけで、「コンパイル時にしか値を計算しない」というわけではない。

## 引数や返り値の`const`と`constexpr`関数

変数の`constexpr`が`const`を兼ねているのでわかりにくいのだが、関数の`constexpr`は引数や返り値の`const`とは一切関係がない。

```C++
#include <iostream>

// OK、常に 42 を返す、 constexpr な関数
constexpr auto answer_constexpr([[maybe_unused]] int question)
{
    return 42;
}

// OK、常に 42 を返す、 constexpr な関数
constexpr auto answer_constexpr2([[maybe_unused]] const int question)
{
    return 42;
}

// OK、与えられた引数に 1 を足して返す、 constexpr な関数
// 受け取る変数を直接書き換えて返すので何も const じゃないように見えるが、れっきとした constexpr 関数である
constexpr auto& answer_constexpr3(int& question)
{
    question += 1;
    return question;
}

// OK、 answer_constexpr3 に直接 constexpr な変数は渡せないが、
// 「const でない変数を渡して constexpr 関数を用いて計算する」こと自体は constexpr
constexpr auto use_answer_constexpr3(int question)
{
    auto q = question;
    q = answer_constexpr3(q);
    return q;
}


int main(){
    constexpr auto value = 41;
    constexpr auto a = answer_constexpr(value);
    constexpr auto b = answer_constexpr2(value);
    constexpr auto c = use_answer_constexpr3(value);
    std::cout << a << ", " << b << ", " << c << std::endl;
    
    return 0;
}
```

上の例では、`answer_constexpr3`関数は「変数の参照を受け取り、破壊的変更を加えて、その参照を`const`も付けずに返す」という、およそ`const`性の欠片もない操作を行っている。
しかしながら、この操作は全て（少なくともC++14以降では）`constexpr`で行ってよい操作なので、この関数は問題なく`constexpr`関数として作ることができる。

## 外部変数の参照

`constexpr`関数で行なってはいけない操作は、主に引数以外の外部の変数を参照する操作である。

```C++
#include <iostream>

// 標準入力から int 型の値を受け取って返す、（コンパイル時に値が定まらない）関数
auto get_value_from_stdin()
{
    int v;
    std::cin >> v;
    return v;
}

namespace sample
{
    const auto outer = get_value_from_stdin(); // 外部の変数
    constexpr auto outer_constexpr = 42; // Constexpr な外部の変数
    
    // constexpr auto f_outer() { return outer; } // NG、外部の変数を参照しているので constexpr 関数にできない
    
    constexpr auto f_outer_constexpr() { return outer_constexpr; } // OK、 constexpr な変数は参照してもよい
    
    constexpr int& f_argument(int& x)
    {
        x += 1; // OK、引数になら色々やってもよい
        return x;
    }
    
    constexpr auto use_f_argument()
    {
        int x = 41;
        return f_argument(x);
    }
}


int main(){
    // constexpr auto a = sample::f_outer(); // NG、 f_outer はコンパイルできない
    constexpr auto b = sample::f_outer_constexpr();
    constexpr auto c = sample::use_f_argument();

    std::cout << /* a << ", " << */ b << ", " << c << std::endl;
    
    return 0;
}
```


## メンバ関数の`constexpr`

メンバ関数にも`constexpr`を付けることができる。
付ける基準は先ほどと同様、「コンパイル時に計算できるかどうか」である。

クラス`C`のメンバ関数

```C++
class C
{
...
public:
...
    auto f(int x, ...) { ... };
};
```

を、

```C++
class C {...};
auto f(C& this, int x, ...) { ... };
```

のように考えれば、`constexpr`を付けるかどうかが判断できるはずだ。

```C++
#include <iostream>

class C
{
public:
    C() = default;
    constexpr C(int a): a(a) {} // メンバ変数の初期化もコンパイル時に可能
    
    // OK、コンパイル時に計算可能
    constexpr auto get() const
    {
        return a;
    }
    
    // OK、コンパイル時に計算可能
    constexpr auto add(const int b)
    {
        a += b;
        return a;
    }
    
    // NG、コンパイル時に標準出力への出力はできない
    constexpr auto print() const
    {
        // std::cout << a << std::endl; // NG
        return a;
    }
    
private:
    int a;
};

constexpr auto use_add(int a)
{
    auto c = C(a);
    c.add(42);
    return c;
}

int main(){
    constexpr auto c = C(42);
    std::cout << c.get() << std::endl;
    constexpr auto c2 = use_add(42);
    std::cout << c2.get() << std::endl;
    c2.print();
    
    return 0;
}
```

## 分割ファイルと`constexpr`と`inline`

`constexpr`関数は、コンパイル時に計算可能でなければならない。
コンパイル時というのは、それぞれの翻訳単位、すなわち各ファイルのコンパイル時に、どのファイルをコンパイルしているときであっても、関数が計算できなければならない、ということである。

従って、`constexpr`関数の宣言だけをして、別ファイルで実装を行なう、といったことはできない。
実装されているファイル以外をコンパイルしているときに、中身が計算できないからである。

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

int main(){
    // OK、 int C1::f(int&) は普通のメンバ関数
    auto c1 = C1(41);
    auto x1 = 1;
    std::cout << c1.f(x1) << std::endl;
    
    /* NG、 int C2::f(int&) は constexpr なのに実装がない
    auto c2 = C2(41);
    auto x2 = 1;
    std::cout << c2.f(x2) << std::endl;
    */
    
    return 0;
}
```

```C++:c.hpp
class C1
{
public:
    constexpr C1(int a): a(a) {}
    int f(int& x);
private:
    int a;
};

class C2
{
public:
    constexpr C2(int a): a(a) {}
    constexpr int f(int& x);
private:
    int a;
};
```

```C++:c.cpp
#include "c.hpp"

int C1::f(int& x) 
{
    x += a;
    return x;
}
constexpr int C2::f(int& x) 
{
    x += a;
    return x;
}
```

というか、`constexpr`関数は自動的に`inline`関数として扱われるので、こういうことになるのである。
あまり意識しなくていいとは思うが、`constexpr`変数も`inline`変数として扱われる。

## `constexpr`テンプレート関数

細かい話になるが、以下のコードはコンパイルできてしまう。

```C++
#include <iostream>

// やりたい放題、明らかに constexpr ではない関数
// 実際、これはコンパイルエラー
/*
constexpr auto print_and_get_int(int t)
{
    std::cout << t << std::endl;
    int v = 0;
    std::cin >> v;
    return v;    
}
*/

// やりたい放題、明らかに constexpr ではないテンプレート関数
// constexpr キーワードをうっかり付けてしまったが、実はコンパイルエラーにはならない
template<typename T>
constexpr auto print_and_get(T t)
{
    std::cout << t << std::endl;
    int v = 0;
    std::cin >> v;
    return v;
}

int main(){
    const auto a = print_and_get(42); // 残念ながら OK、constexprキーワードは無視される
    // constexpr auto b = print_and_get(42); // NG、constexpr で受けるとコンパイルエラー
    std::cout << a << /* ", " << b << */ std::endl;
    
    return 0;
}
```

実は、`constexpr`かつテンプレートな関数を作った場合、その関数を実体化した時に`constexpr`関数として不適格なことが判明しても、コンパイルエラーにはならず、単にその関数が非`constexpr`関数として扱われるのだ。
この場合、`print_and_get`関数はどのように実体化しても`constexpr`関数にはならない。が、コンパイルエラーにもならない。単に`constexpr`キーワードが無視される。

なので、テンプレート関数に`constexpr`キーワードがついていても、プログラマは「もしかしたらこの関数は（常に）`constexpr`関数ではないかもしれない」と身構えなければならない。
おそらく、テンプレートのいくつかの場合で`constexpr`で、かつ他の場合で`constexpr`ではないような関数を作りたい、という需要があるからだと思うのだが、そこは特殊化で対応してほしかったという気持ちである。

## `constexpr`なラムダ式

ラムダ式（の`operator()`）も、関数と同様に`constexpr`に指定することができる。
というか、指定しなくても勝手に`constexpr`になってくれる。

```C++
#include <iostream>

// 標準入力から int 型の値を受け取って返す、（コンパイル時に値が定まらない）関数
auto get_value_from_stdin()
{
    int v;
    std::cin >> v;
    return v;
}

int main(){
    // 常に 42 を返す lambda 式
    auto lambda = []{ return 42; };
    // 常に 42 を返す、 constexpr を明示した lambda 式
    auto lambda_constexpr = []() constexpr { return 42; };
    // constexpr な変数はキャプチャ可能
    constexpr auto outer_value = 42;
    auto lambda_capture = [&outer_value]{ return outer_value; };
    // constexpr でない変数をキャプチャした場合、 constexpr 関数にはならない
    const auto outer_value2 = get_value_from_stdin();
    auto lambda_bad = [&outer_value2]{ return outer_value2; };

    constexpr auto a = lambda(); // constexpr 関数であることを明示しなくても使える
    constexpr auto b = lambda_constexpr(); // 当然明示してもよい
    constexpr auto c = lambda_capture(); // OK、この関数は constexpr 関数
    // constexpr auto d = lambda_bad(); // NG、この関数は constexpr 関数ではない
    std::cout << a << ", " <<  b <<  ", " << c << /* ", " << d << */ std::endl;

    return 0;
}
```


# まとめ

+ `constexpr`は、変数と関数で挙動が大きく異なるので、同一視はしないほうがよい。
+ `constexpr`関数と`const`修飾子にあんまり関係がない点にも注意したほうがよい。
+ 複雑な関数の場合、「外部に影響を与えるか」「`inline`にできるか」の2点を考えれば、その関数が`constexpr`かどうかがわかるはずである。
+ 思っているよりはるかに`constexpr`にできる関数の幅は広いので、どんどん使っていくべき。ただしテンプレート関数を`constexpr`にするときは注意が必要。