12,982
回編集
270行目: | 270行目: | ||
</source> | </source> | ||
===== メンバ関数のconstexpr ===== | ===== メンバ関数のconstexpr ===== | ||
メンバ関数にも<code>constexpr</code>を付けることができる。<br> | |||
<code>constexpr</code>を付ける基準は先ほどと同様、「コンパイル時に計算できるかどうか」である。<br> | |||
<source lang="c++"> | <source lang="c++"> | ||
class CHoge | class CHoge | ||
281行目: | 281行目: | ||
}; | }; | ||
</source> | </source> | ||
を、以下のように考えれば、constexprを付けるかどうかが判断できる。 | を、以下のように考えれば、constexprを付けるかどうかが判断できる。<br> | ||
<source lang="c++"> | <source lang="c++"> | ||
class | class CHoge {...}; | ||
auto f(C& this, int x, ...) { ... }; | auto f(C& this, int x, ...) { ... }; | ||
</source> | </source> | ||
338行目: | 338行目: | ||
} | } | ||
</source> | </source> | ||
<br> | |||
===== 分割ファイルとconstexprとインライン ===== | |||
constexprが付いている関数は、コンパイル時に計算可能でなければならない。<br> | |||
コンパイル時というのはそれぞれの翻訳単位、すなわち、各ファイルをコンパイルしている場合でも関数が計算できなければならないということである。<br> | |||
<br> | |||
したがって、<code>constexpr</code>が付いている関数の宣言だけをして、別ファイルで実装を行うことはできない。<br> | |||
実装されているファイル以外をコンパイルしている時に、関数の内容が計算できないからである。<br> | |||
<br> | |||
つまり、<code>constexpr</code>が付いている関数は自動的にinline関数として扱われるということになる。<br> | |||
また、<code>constexpr</code>が付いている変数もinline変数として扱われる。<br> | |||
<source lang="c++"> | |||
// main.cpp | |||
#include <iostream> | |||
#include "main.h" | |||
int main() | |||
{ | |||
// コンパイル可能 int CHoge1::f(int&)は通常のメンバ関数 | |||
auto c1 = CHoge1(41); | |||
auto x1 = 1; | |||
std::cout << c1.f(x1) << std::endl; | |||
// コンパイルエラー int CHoge2::f(int&)はconstexprであるが実装が無い | |||
// auto c2 = C2(41); | |||
// auto x2 = 1; | |||
// std::cout << c2.f(x2) << std::endl; | |||
return 0; | |||
} | |||
</source> | |||
<br> | |||
<source lang="c++"> | |||
// main.h | |||
class CHoge1 | |||
{ | |||
private: | |||
int a; | |||
public: | |||
constexpr CHoge1(int a) : a(a) {} | |||
int f(int& x); | |||
}; | |||
class CHoge2 | |||
{ | |||
private: | |||
int a; | |||
public: | |||
constexpr CHoge2(int a) : a(a) {} | |||
constexpr int f(int& x); | |||
}; | |||
</source> | |||
<br> | |||
<source lang="c++"> | |||
// sub.cpp | |||
#include "main.h" | |||
int CHoge1::f(int& x) | |||
{ | |||
x += a; | |||
return x; | |||
} | |||
constexpr int CHoge2::f(int& x) | |||
{ | |||
x += a; | |||
return x; | |||
} | |||
</source> | |||
<br> | |||
===== constexprテンプレート関数 ===== | |||
constexprが付いているテンプレート関数の場合、その関数を実体化した時にconstexpr関数として不適格な場合でもコンパイルエラーにはならず、非constexpr関数として扱われる。<br> | |||
以下のサンプルコードの場合、print_and_get関数はどのように実体化してもconstexpr関数にはならないが、コンパイルエラーにもならずに、constexprキーワードが無視される。<br> | |||
<br> | |||
つまり、テンプレート関数にconstexprキーワードが付いていても、設計者は「関数はconstexpr関数ではないかもしれない」と身構えなければならない。<br> | |||
<br> | |||
おそらく、「テンプレートのいくつかの場合はconstexpr、他の場合はconstexprではないような関数を作成したい」という需要があるからだと推測する。<br> | |||
<source lang="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); // コンパイル可能 constexprキーワードは無視される | |||
// constexpr auto b = print_and_get(42); // コンパイルエラー constexprで受けるとコンパイルエラー | |||
std::cout << a << /* ", " << b << */ std::endl; | |||
return 0; | |||
} | |||
</source> | |||
<br> | |||
===== constexprが付いているラムダ式 ===== | |||
ラムダ式(のoperator())も、関数と同様にconstexprを指定することができる。(指定しなくとも自動的にconstexprが付けてくれる)<br> | |||
<source lang="c++"> | |||
#include <iostream> | |||
// 標準入力からint型の値を受け取って返す(コンパイル時に値が定まらない)関数 | |||
auto get_value_from_stdin() | |||
{ | |||
int v; | |||
std::cin >> v; | |||
return v; | |||
} | |||
int main() | |||
{ | |||
// 常に42を返すラムダ式 | |||
auto f = []{ return 42; }; | |||
// 常に42を返すconstexprを明示したラムダ式 | |||
auto f_constexpr = []() constexpr { return 42; }; | |||
// constexprが付いている変数はキャプチャ可能 | |||
constexpr auto outer_value = 42; | |||
auto f_capture = [&outer_value]{ return outer_value; }; | |||
// constexprが付いている変数をキャプチャした場合は、constexpr関数にはならない | |||
const auto outer_value2 = get_value_from_stdin(); | |||
auto f_bad = [&outer_value2]{ return outer_value2; }; | |||
constexpr auto a = f(); // constexpr関数であることを明示しなくても使用可能 | |||
constexpr auto b = f_constexpr(); // constexpr関数であることを明示しても使用可能 | |||
constexpr auto c = f_capture(); // コンパイル可能 この関数はconstexpr関数 | |||
// constexpr auto d = f_bad(); // コンパイルエラー この関数はconstexpr関数ではない | |||
std::cout << a << ", " << b << ", " << c << /* ", " << d << */ std::endl; | |||
return 0; | |||
} | |||
</source> | |||
<br><br> | |||
== まとめ == | |||
* constexprは、変数と関数で挙動が大きく異なるので、同一視しない。 | |||
* constexpr関数とconst修飾子にあまり関係がない点にも注意する。 | |||
* 複雑な関数の場合、「外部に影響を与えるか」「inlineにできるか」の2点を考えれば、その関数がconstexprが必要かどうか分かる。 | |||
* constexprを付けることができる関数の幅は広いので使用するべきである。<br>ただし、テンプレート関数をconstexprにするときは注意が必要である。 | |||
<br><br> | <br><br> | ||
__FORCETOC__ | __FORCETOC__ | ||
[[カテゴリ:C++]] | [[カテゴリ:C++]] |