Skip to content

Commit bb18d67

Browse files
committed
C++23 : if constevalを追加 (close #1036)
1 parent 8250474 commit bb18d67

File tree

4 files changed

+142
-21
lines changed

4 files changed

+142
-21
lines changed

implementation-status.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,7 @@
265265
| P2071R2: [名前付きユニバーサルキャラクタ名](/lang/cpp23/named_universal_character_escapes.md) | 16進数のユニバーサルキャラクタだけでなく、その文字の名前を入力できるようにする | 13 | 15 | 2023.2 | - |
266266
| P2096R2: [部分特殊化の汎用化仕様](/lang/cpp23/generalized_wording_for_partial_specializations.md.nolink) | 変数テンプレートの部分特殊化を許可するために部分特殊化の仕様を汎用化 | - | - | - | - |
267267
| P2582R1: [継承コンストラクタからのクラステンプレート引数の推論](/lang/cpp23/class_template_argument_deduction_from_inherited.md) | 継承コンストラクタからもクラステンプレート引数を推論できるようにする | - | - | - | - |
268-
| P1938R3: [`if consteval`](/lang/cpp23/if_consteval.md.nolink) | コンパイル時の文脈かどうかで分岐させる | 12 | 14 | - | - |
268+
| P1938R3: [`if consteval`](/lang/cpp23/if_consteval.md) | コンパイル時の文脈かどうかで分岐させる | 12 | 14 | - | - |
269269
| P1401R5: [定数式の文脈での`bool`への縮小変換を許可](/lang/cpp23/narrowing_contextual_conversions_to_bool.md) | `if constexpr(flags & Flags::Exec)``static_assert(N);`を許可 | 9 | 13 | 2022.2 | - |
270270
| P2242R3: [定数式内での非リテラル変数、静的変数・スレッドローカル変数およびgotoとラベルの存在を許可する](/lang/cpp23/non_literal_variables_in_constexpr_functions.md) | コンパイル時に評価されない限り、定数式内に静的変数・スレッドローカル変数およびgoto文とラベルを含むことを許可する | 12 | 15 | 2022.2 | - |
271271
| P2246R1: [静的な診断メッセージの文字エンコーディング](/lang/cpp23/character_encoding_of_diagnostic_text.md) | `static_assert``[[deprecated]]`などの診断メッセージの文字集合に関する要件をなくす | - | yes | - | 2022 |

lang/cpp23.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ C++23とは、2023年中に改訂される予定の、C++バージョンの通
6767

6868
| 言語機能 | 説明 |
6969
|----------|------|
70-
| [`if consteval`](cpp23/if_consteval.md.nolink) | コンパイル時の文脈かどうかで分岐させる |
70+
| [`if consteval`](cpp23/if_consteval.md) | コンパイル時の文脈かどうかで分岐させる |
7171
| [定数式の文脈での`bool`への縮小変換を許可](cpp23/narrowing_contextual_conversions_to_bool.md) | `if constexpr(flags & Flags::Exec)``static_assert(N);`を許可 |
7272
| [定数式内での非リテラル変数、静的変数・スレッドローカル変数およびgotoとラベルの存在を許可する](cpp23/non_literal_variables_in_constexpr_functions.md) | コンパイル時に評価されない限り、定数式内に静的変数・スレッドローカル変数およびgoto文とラベルを含むことを許可する |
7373
| [静的な診断メッセージの文字エンコーディング](cpp23/character_encoding_of_diagnostic_text.md) | `static_assert``[[deprecated]]`などの診断メッセージの文字集合に関する要件をなくす |

lang/cpp23/if_consteval.md

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
# if consteval
2+
* cpp23[meta cpp]
3+
4+
## 概要
5+
C++23では、`constexpr`関数がコンパイル時に呼ばれたかを判定するための構文として、`if consteval`を導入する。これは「consteval if文」と呼ばれる。
6+
7+
```cpp
8+
constexpr double my_fma(double a, double b, double c) {
9+
if consteval {
10+
// コンパイル時に実行される文脈
11+
return a * b + c;
12+
}
13+
else {
14+
// 実行時に実行される文脈
15+
return __builtin_fma(a, b, c);
16+
}
17+
}
18+
19+
#include <iostream>
20+
int main() {
21+
constexpr double static_fma = my_fma(1.0, 2.0, 3.0);
22+
double dynamic_fma = my_fma(1.0, 2.0, 3.0);
23+
24+
std::cout << static_fma << std::endl;
25+
std::cout << dynamic_fma << std::endl;
26+
}
27+
```
28+
29+
`if consteval`文内は即時関数 (`consteval`) の文脈であり、`consteval`関数を呼び出すことができる。
30+
31+
`if consteval`の逆条件として、実行時かを判定するためには、`if !consteval`を使用する (`consteval`の前に`!`)。
32+
33+
```cpp
34+
constexpr double my_fma(double a, double b, double c) {
35+
if !consteval {
36+
// 実行時に実行される文脈
37+
return __builtin_fma(a, b, c);
38+
}
39+
else {
40+
// コンパイル時に実行される文脈
41+
return a * b + c;
42+
}
43+
}
44+
```
45+
46+
`if consteval`文は複合文でなければならない。
47+
48+
```cpp
49+
constexpr double my_fma(double a, double b, double c) {
50+
if !consteval {
51+
return __builtin_fma(a, b, c);
52+
}
53+
else
54+
return a * b + c; // コンパイルエラー!複合文でなければならない
55+
}
56+
```
57+
58+
## この機能が必要になった背景・経緯
59+
### constexprとconstevalの相互作用での問題
60+
`consteval`を使用したプログラムを考える。
61+
62+
```cpp
63+
consteval int f(int i) { return i; }
64+
65+
constexpr int g(int i) {
66+
if (std::is_constant_evaluated()) {
67+
return f(i) + 1; // <==
68+
} else {
69+
return 42;
70+
}
71+
}
72+
73+
consteval int h(int i) {
74+
return f(i) + 1;
75+
}
76+
```
77+
* std::is_constant_evaluated[link /reference/type_traits/is_constant_evaluated.md]
78+
79+
定数式評価時には、関数`g()`と関数`h()`は同じことをするが、実行時には関数`h()`を呼び出すことはできない。また、`if (std::is_constant_evaluated())`文内は定数式ではあるが即時関数の文脈ではないため、即時関数`f()`を呼び出すことはできず、`<==`コメントのところで不適格となる。この文脈では、`f(42)`を呼び出すことはできるが、`f(i)`を呼び出すことはできない。
80+
81+
82+
83+
### `if constexpr (std::is_constant_evaluated())`の問題
84+
もうひとつの問題は、[`std::is_constant_evaluated()`](/reference/type_traits/is_constant_evaluated.md)固有の問題だが、この関数は`if`文と使わなければならないが、誤って`if constexpr`で使用してしまう場合である。
85+
86+
```cpp
87+
constexpr size_t strlen(char const* s) {
88+
if constexpr (std::is_constant_evaluated()) {
89+
for (const char *p = s; ; ++p) {
90+
if (*p == '\0') {
91+
return static_cast<std::size_t>(p - s);
92+
}
93+
}
94+
} else {
95+
__asm__("SSE 4.2で計算…");
96+
}
97+
}
98+
```
99+
100+
この関数は常にコンパイル時の方の文脈が呼ばれてしまう。この使用間違いはコンパイラによっては警告を出力するが、誤用を防ぐのがむずかしいという問題があった。
101+
102+
103+
104+
## 関連項目
105+
- [`std::is_constant_evaluated()`](/reference/type_traits/is_constant_evaluated.md)
106+
107+
108+
## 参照
109+
- [P1938R3 `if consteval`](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p1938r3.html)

reference/type_traits/is_constant_evaluated.md

Lines changed: 31 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -17,24 +17,34 @@ namespace std {
1717
1818
1919
## 戻り値
20-
本関数は以下の文脈内で評価された場合に`true`を返す。
21-
22-
- 文法上の定数式(配列型の要素数、`case`ラベルの値、など)
23-
- [`constexpr if`](/lang/cpp17/if_constexpr.md)の条件式
24-
- [`consteval`関数](/lang/cpp20/immediate_functions.md)の呼び出し内
25-
- [コンセプト](/lang/cpp20/concepts.md)の定義式
26-
- `requires`節内
27-
- 入れ子要件内
28-
- 定数式で使用可能な変数の初期化式
29-
- `constexpr`変数の初期化式
30-
- 定数初期化される`const`な整数、列挙型変数の初期化式
31-
- 定数初期化される参照型変数の初期化式
32-
- 定数初期化される変数の初期化式
33-
34-
上記の文脈の外では、コンパイラの最適化(定数畳み込み)によって容易にコンパイル時評価できる式や`constexpr`関数の呼び出し中の評価であっても`false`となる。
35-
36-
[`constexpr if`](/lang/cpp17/if_constexpr.md)の条件式および[`static_assert`](/lang/cpp11/static_assert.md)の条件式に書かれている場合は必ず`true`となるので注意が必要である。
37-
20+
- C++20:
21+
本関数は以下の文脈内で評価された場合に`true`を返す。
22+
23+
- 文法上の定数式(配列型の要素数、`case`ラベルの値、など)
24+
- [`constexpr if`](/lang/cpp17/if_constexpr.md)の条件式
25+
- [`consteval`関数](/lang/cpp20/immediate_functions.md)の呼び出し内
26+
- [コンセプト](/lang/cpp20/concepts.md)の定義式
27+
- `requires`節内
28+
- 入れ子要件内
29+
- 定数式で使用可能な変数の初期化式
30+
- `constexpr`変数の初期化式
31+
- 定数初期化される`const`な整数、列挙型変数の初期化式
32+
- 定数初期化される参照型変数の初期化式
33+
- 定数初期化される変数の初期化式
34+
35+
上記の文脈の外では、コンパイラの最適化(定数畳み込み)によって容易にコンパイル時評価できる式や`constexpr`関数の呼び出し中の評価であっても`false`となる。
36+
37+
[`constexpr if`](/lang/cpp17/if_constexpr.md)の条件式および[`static_assert`](/lang/cpp11/static_assert.md)の条件式に書かれている場合は必ず`true`となるので注意が必要である。
38+
39+
- C++23:
40+
以下と等価:
41+
```cpp
42+
if consteval {
43+
return true;
44+
} else {
45+
return false;
46+
}
47+
```
3848
3949
## 備考
4050
@@ -134,7 +144,9 @@ int main()
134144
135145
## 関連項目
136146
- [C++20 コンパイル時初期化を強制する`constinit`キーワードを追加](/lang/cpp20/constinit.md)
137-
- [C++23 `if consteval`](/lang/cpp23/if_consteval.md.nolink)
147+
- [C++23 `if consteval`](/lang/cpp23/if_consteval.md)
138148
139149
## 参照
140150
- [P0595R2 `std::is_constant_evaluated()`](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0595r2.html)
151+
- [P1938R3 `if consteval`](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p1938r3.html)
152+
- C++23で`if consteval`を導入したことにより、この関数の効果を`if consteval`ベースの仕様に変更

0 commit comments

Comments
 (0)