「C++の基礎 - constexpr」の版間の差分
(ページの作成:「== 概要 == C++には、<code>constexpr</code>という概念がある。<br> <br> * constexprが使用できない、または、使用すべきではない場合 **…」) |
|||
265行目: | 265行目: | ||
std::cout << /* a << ", " << */ b << ", " << c << std::endl; | std::cout << /* a << ", " << */ b << ", " << c << std::endl; | ||
return 0; | |||
} | |||
</source> | |||
===== メンバ関数のconstexpr ===== | |||
メンバ関数にもconstexprを付けることができる。<br> | |||
付ける基準は先ほどと同様、「コンパイル時に計算できるかどうか」である。<br> | |||
<source lang="c++"> | |||
class CHoge | |||
{ | |||
// ... | |||
public: | |||
// ... | |||
auto f(int x, ...) { ... }; | |||
}; | |||
</source> | |||
を、以下のように考えれば、constexprを付けるかどうかが判断できる。 | |||
<source lang="c++"> | |||
class C {...}; | |||
auto f(C& this, int x, ...) { ... }; | |||
</source> | |||
<br> | |||
<source lang="c++"> | |||
#include <iostream> | |||
class CHoge | |||
{ | |||
private: | |||
int a; | |||
public: | |||
CHoge() = default; | |||
constexpr CHoge(int a): a(a) {} // メンバ変数の初期化もコンパイル時に可能 | |||
// コンパイル可能 コンパイル時に計算可能 | |||
constexpr auto get() const | |||
{ | |||
return a; | |||
} | |||
// コンパイル可能 コンパイル時に計算可能 | |||
constexpr auto add(const int b) | |||
{ | |||
a += b; | |||
return a; | |||
} | |||
constexpr auto print() const | |||
{ | |||
// std::cout << a << std::endl; // コンパイルエラー コンパイル時に標準出力への出力はできない | |||
return a; | |||
} | |||
}; | |||
constexpr auto use_add(int a) | |||
{ | |||
auto c = CHoge(a); | |||
c.add(42); | |||
return c; | |||
} | |||
int main() | |||
{ | |||
constexpr auto c = CHoge(42); | |||
std::cout << c.get() << std::endl; | |||
constexpr auto c2 = use_add(42); | |||
std::cout << c2.get() << std::endl; | |||
c2.print(); | |||
return 0; | return 0; |
2020年8月17日 (月) 11:26時点における版
概要
C++には、constexpr
という概念がある。
- constexprが使用できない、または、使用すべきではない場合
- 変数
- constでない変数
- クラスのメンバ変数
- 標準入力などの非constexpr関数を使用して計算する値
- 引数等のconstexprでない可能性がある値を使用して計算する値
- 関数
- インライン化できない関数
- 引数でもthisでもない非constexprな外側の変数を参照する操作を含む関数
- 引数でもthisでもない外側に副作用を及ぼすような操作を含む関数
- 変数
- constexprを使用すべき場合
- 上記以外全て
変数のconstexpr
変数におけるconstexprは、#define
等で作成するようなコンパイル時定数を作るためのキーワードである。
constとは「この変数は変更しないため、変更しようとする場合はコンパイルエラーにする」という合図であるのに対して、
constexprとは「この変数の値はコンパイル時に確定するため、確定しない場合はコンパイルエラーにする」という合図である。
また、constexpr変数は、const変数としても扱われる。
コンパイラは、constexprがついた変数の値をコンパイル時に計算しようとする。もし、計算できなければ、コンパイルエラーを出力する。
以下のサンプルコードでは、"標準入力から受け取る"という操作がコンパイル時に行えないため、コンパイルエラーとなる。
どの操作がコンパイル時に計算可能かは、関数にconstexprキーワードが付いているかどうかで判断される。(後述のセクションで記載する)
#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; // コンパイル可能 aは後から書き換えてよい
// b = 5; // コンパイルエラー bはconstなので書き換えてはいけない
// c = 6; // コンパイルエラー cはconstなので書き換えてはいけない
std::cout << a << ", " << b << ", " << c << std::endl;
auto d = get_value_from_stdin(); // コンパイル可能 dは実行時に受け取った値
const auto e = get_value_from_stdin(); // コンパイル可能 eは実行時に受け取り、今後変更されない値
// constexpr auto f = get_value_from_stdin(); // コンパイルエラー fはコンパイル時に値が確定しなければならない
std::cout << d << ", " << e << /* ", " << f << */ std::endl;
return 0;
}
関数のconstexpr
constexpr変数への戻り値の代入
constexprは関数にも付けることができる。
関数に付けたconstexprキーワードは、この関数はコンパイル時に計算できることを表している。
constexpr変数に入れられる値は、コンパイル時に計算できる値だけである。
そのため、constexprキーワードの無い関数の戻り値をconstexpr変数にする時、コンパイラはこの値はコンパイル時には計算できないと考えて、コンパイルエラーを出力する。
また、constexprキーワードがある関数の戻り値においても、関数の内容を見て値を計算して、その過程で同様にコンパイル時には計算できない式が1つでもあれば、コンパイルエラーを出力する。
#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; // コンパイルエラー 標準出力への書き出しはコンパイル時には行えない
return 42;
}
int main()
{
// constexpr auto a = answer(); // コンパイルエラー answer関数はconstexpr関数ではない
constexpr auto b = answer_constexpr(); // コンパイル可能 answer_constexpr関数はconstexpr関数
// constexpr auto c = answer_stdin(); // コンパイルエラー answer_stdin関数はコンパイルできない
std::cout << /* a << ", " << */ b << /* ", " << c << */ std::endl;
return 0;
}
また、コンパイル時に計算されているかどうか確認する場合は、static_assert
を使用する方法もある。
// 常に42を返す関数
auto answer()
{
return 42;
}
// 常に42を返すconstexprな関数
constexpr auto answer_constexpr()
{
return 42;
}
int main()
{
// static_assert(answer() || true); // コンパイルエラー answer()はコンパイル時に計算できない
static_assert(answer_constexpr() || true); // コンパイル可能 answer_constexpr()はconstexpr関数
return 0;
}
constexprではない引数を与える
constexpr関数の結果は、常にコンパイル時に計算されるというわけではない。
以下のサンプルコードでは、constexprが付いているanswer_constexpr関数の引数questionはconstexprではないが、
この場合でもコンパイル可能で、answer_constexpr関数はあたかもconstexprキーワードの無い関数のように(少なくとも(1)の行では)動作する。
constexprキーワードはあくまで「コンパイル時に値が確定できる」ことを伝えるだけで、「コンパイル時にしか値を計算しない」というわけではない。
#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); // コンパイル可能 引数がconstexprなので、コンパイル時に値が確定する
// constexpr auto b = answer_constexpr(value_const); // コンパイルエラー 引数がconstexprな値ではないので、コンパイル時に値が確定しない
const auto c = answer_constexpr(value_constexpr); // コンパイル可能 引数がconstexprなので、コンパイル時に値が確定する
const auto d = answer_constexpr(value_const); // (1) コンパイル可能 引数がconstexprな値ではないので、コンパイル時に値は確定しないが、コンパイルエラーにはならない
std::cout << a << ", " << /* b << ", " << */ c << ", " << d << std::endl;
return 0;
}
引数や戻り値のconstとconstexpr関数
変数のconstexprがconstを兼ねているので見づらいが、関数のconstexprは引数や返り値のconstとは一切関係が無い。
以下のサンプルコードでは、answer_constexpr3関数は「変数の参照を受け取り、破壊的変更を加えて、その参照をconstも付けずに返す」という操作を行っているが、
この操作は全て(少なくともC++14以降では)constexprで行ってよい操作なので、この関数は問題なくconstexpr関数として作ることができる。
#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;
}
外部変数の参照
constexpr関数で行なってはいけない操作は、主に引数以外のconstexpr以外の外部の変数を参照する操作である。
#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関数にできない
// constexpr auto f_outer()
// {
// return outer;
// }
// コンパイル可能 constexprな外部の変数は参照してもよい
constexpr auto f_outer_constexpr()
{
return outer_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(); // コンパイルエラー 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を付けることができる。
付ける基準は先ほどと同様、「コンパイル時に計算できるかどうか」である。
class CHoge
{
// ...
public:
// ...
auto f(int x, ...) { ... };
};
を、以下のように考えれば、constexprを付けるかどうかが判断できる。
class C {...};
auto f(C& this, int x, ...) { ... };
#include <iostream>
class CHoge
{
private:
int a;
public:
CHoge() = default;
constexpr CHoge(int a): a(a) {} // メンバ変数の初期化もコンパイル時に可能
// コンパイル可能 コンパイル時に計算可能
constexpr auto get() const
{
return a;
}
// コンパイル可能 コンパイル時に計算可能
constexpr auto add(const int b)
{
a += b;
return a;
}
constexpr auto print() const
{
// std::cout << a << std::endl; // コンパイルエラー コンパイル時に標準出力への出力はできない
return a;
}
};
constexpr auto use_add(int a)
{
auto c = CHoge(a);
c.add(42);
return c;
}
int main()
{
constexpr auto c = CHoge(42);
std::cout << c.get() << std::endl;
constexpr auto c2 = use_add(42);
std::cout << c2.get() << std::endl;
c2.print();
return 0;
}