From f1090f2b7c4a8027b317fdc266cc7598caeb2ba8 Mon Sep 17 00:00:00 2001 From: yumetodo Date: Sun, 17 Mar 2024 15:34:30 +0900 Subject: [PATCH 1/7] =?UTF-8?q?refactor:=20CWG2518=E9=81=A9=E7=94=A8?= =?UTF-8?q?=E3=82=92=E5=89=8D=E6=8F=90=E3=81=A8=E3=81=97=E3=81=A6=E8=A8=98?= =?UTF-8?q?=E8=BF=B0=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ref - https://github.com/cpprefjp/site/commit/79fc60a46544883f2f132639a33ff68adb99e3d4#r139029850 - https://github.com/cpprefjp/site/issues/1257#issuecomment-1964098638 --- lang/cpp17/if_constexpr.md | 116 +++++++++++++++++++++++-------------- 1 file changed, 72 insertions(+), 44 deletions(-) diff --git a/lang/cpp17/if_constexpr.md b/lang/cpp17/if_constexpr.md index 2b218c451e..29debaf9b6 100644 --- a/lang/cpp17/if_constexpr.md +++ b/lang/cpp17/if_constexpr.md @@ -68,11 +68,60 @@ void f(Out& o, A1 const& a1, A2 const& a2) それでも記述が実際にしたい処理に比べて不必要に複雑になる。 constexpr if文の導入によりそのような複雑な手法を用いずに素直に条件付きのコンパイルを実現できるようになった。 +`constexpr if`文の条件式内は実体化が起きる。したがって実体化するとコンパイルエラーになるものは書いてはいけない。 + +```cpp example +#include +#include + +struct Hoge { + using type = int; +}; + +template +void f() +{ + if constexpr (std::is_same_v || std::is_same_v) { + std::cout << "is int" << std::endl; + } +} + +int main() +{ + f(); //error: Hoge::value_typeは存在しないのでif constexpr文の条件式がコンパイルエラーになる +} +``` + +### `static_assert`文に関する例外 + +後に述べる2段階名前探索に関する注意点とは関係なく、C++23以降、もしくはCWG 2518が適用された環境においては、template文(もしくは適切な特殊化や`constexpr if`文の中の文)が実際にインスタンス化されるまで、`static_assert`文の宣言は遅延される。 + +```cpp example +#include +template +void f(T t) { + if constexpr (sizeof(T) == sizeof(std::int32_t)) { + use(t); + } else { + static_assert(false, "must be 32bit"); + } +} + +void g(std::int8_t c) { + std::int32_t n = 0; + f(n); // OK: nはstd::int32_t型なので`use(t);`のほうがインスタンス化されるために、static_assert文は宣言されない。 + f(c); // error: cはstd::int8_t型なので、static_assert文は宣言され、"must be 32bit"とコンパイラが診断メッセージを出力する +} +``` + ### 2段階名前探索における注意点 `constexpr if`文で、実行されない方の`statement`は廃棄文(discarded statement)となり、文の実体化を防ぐ。言い換えると、2段階名前探索における依存名(dependent name)は、廃棄文の場合検証されない。また文が実体化されないのだから通常のif文と同じくもちろん実行時に実行もされない。つまり次の例は意図と異なる挙動を示す。 -```cpp example +```cpp +template struct inferior_static_assert_failure; +template<> struct inferior_static_assert_failure{ enum { value = 1 }; }; +#define INFERIOR_STATIC_ASSERT(B) typedef char inferior_static_assert[sizeof(inferior_static_assert_failure::value)] #include template @@ -82,7 +131,7 @@ void f(T) { // Tがintのときのみ評価されてほしい // 実際は常に評価される - static_assert(false); + INFERIOR_STATIC_ASSERT(false); } } @@ -93,21 +142,19 @@ int main() } ``` -なぜならば廃棄文はテンプレートの実体化を防ぐ (依存名の検証をしない) だけで、非依存名は検証されるからである。この例の[`static_assert`](/lang/cpp11/static_assert.md)に渡す条件式はテンプレートパラメータに依存していないので、テンプレートの宣言時に検証され、エラーとなる。言い換えれば`static_assert`に渡す条件式が依存名ならばテンプレートの宣言時に検証されず、テンプレート実体化まで評価を遅らせることができる。 +CWG 2518が適用されていない環境においては`static_assert`でも同じ現象が発生する。 ```cpp example #include -template -constexpr bool false_v = false; - template void f(T) { if constexpr (std::is_same_v) { - // Tがintのときのみ評価される - static_assert(false_v); + // Tがintのときのみ評価されてほしい + // 実際は常に評価される + static_assert(false); } } @@ -118,18 +165,25 @@ int main() } ``` -上の例では`false_v`を作ったが、ラムダ式でも同様のことができる。ラムダ式はそれが記述された位置から見て最小のスコープ (ブロックスコープ/クラススコープ/名前空間スコープ) で宣言されるクラスとして扱われる。例えば、下の例では`f()`という関数テンプレート内で宣言される。関数テンプレート内のクラスは依存名になるため、テンプレートの宣言時に検証されず、テンプレート実体化まで評価を遅らせることができる。 +なぜならば廃棄文はテンプレートの実体化を防ぐ (依存名の検証をしない) だけで、非依存名は検証されるからである。この例の[`static_assert`](/lang/cpp11/static_assert.md)に渡す条件式はテンプレートパラメータに依存していないので、テンプレートの宣言時に検証され、エラーとなる。 + +#### CWG 2518が適用されていない環境でのワークアラウンド + +言い換えれば`static_assert`に渡す条件式が依存名ならばテンプレートの宣言時に検証されず、テンプレート実体化まで評価を遅らせることができる。 ```cpp example #include +template +constexpr bool false_v = false; + template void f(T) { if constexpr (std::is_same_v) { // Tがintのときのみ評価される - static_assert([]{return false;}()); + static_assert(false_v); } } @@ -140,51 +194,25 @@ int main() } ``` -`constexpr if`文の条件式内は実体化が起きる。したがって実体化するとコンパイルエラーになるものは書いてはいけない。 +上の例では`false_v`を作ったが、ラムダ式でも同様のことができる。ラムダ式はそれが記述された位置から見て最小のスコープ (ブロックスコープ/クラススコープ/名前空間スコープ) で宣言されるクラスとして扱われる。例えば、下の例では`f()`という関数テンプレート内で宣言される。関数テンプレート内のクラスは依存名になるため、テンプレートの宣言時に検証されず、テンプレート実体化まで評価を遅らせることができる。 ```cpp example #include -#include - -struct Hoge { - using type = int; -}; template -void f() +void f(T) { - if constexpr (std::is_same_v || std::is_same_v) { - std::cout << "is int" << std::endl; + if constexpr (std::is_same_v) + { + // Tがintのときのみ評価される + static_assert([]{return false;}()); } } int main() { - f(); //error: Hoge::value_typeは存在しないのでif constexpr文の条件式がコンパイルエラーになる -} -``` - -### (CWG 2518が適用された環境) `static_assert`文に関する例外 - -上に述べたように、`constexpr if`文の中の文は廃棄文においても、非依存名の検証を行う。このため特に`static_assert`文を使う時に直感的ではない挙動を示していた。 - -C++23以降、もしくはCWG 2518が適用された環境においては、template文(もしくは適切な特殊化や`constexpr if`文の中の文)が実際にインスタンス化されるまで、`static_assert`文の宣言は遅延される。 - -```cpp example -#include -template -void f(T t) { - if constexpr (sizeof(T) == sizeof(std::int32_t)) { - use(t); - } else { - static_assert(false, "must be 32bit"); - } -} - -void g(std::int8_t c) { - std::int32_t n = 0; - f(n); // OK: nはstd::int32_t型なので`use(t);`のほうがインスタンス化されるために、static_assert文は宣言されない。 - f(c); // error: cはstd::int8_t型なので、static_assert文は宣言され、"must be 32bit"とコンパイラが診断メッセージを出力する + f(2.4); + f(3); } ``` From 39695e1d9b2da46cb2a42eabb043abc095c1aee3 Mon Sep 17 00:00:00 2001 From: yumetodo Date: Mon, 18 Mar 2024 09:58:49 +0900 Subject: [PATCH 2/7] =?UTF-8?q?chore:=20s/=E3=83=AF=E3=83=BC=E3=82=AF?= =?UTF-8?q?=E3=82=A2=E3=83=A9=E3=82=A6=E3=83=B3=E3=83=89/=E5=9B=9E?= =?UTF-8?q?=E9=81=BF=E7=AD=96/=20(#1260)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Koichi Murase --- lang/cpp17/if_constexpr.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lang/cpp17/if_constexpr.md b/lang/cpp17/if_constexpr.md index 29debaf9b6..cb9330d5ab 100644 --- a/lang/cpp17/if_constexpr.md +++ b/lang/cpp17/if_constexpr.md @@ -167,7 +167,7 @@ int main() なぜならば廃棄文はテンプレートの実体化を防ぐ (依存名の検証をしない) だけで、非依存名は検証されるからである。この例の[`static_assert`](/lang/cpp11/static_assert.md)に渡す条件式はテンプレートパラメータに依存していないので、テンプレートの宣言時に検証され、エラーとなる。 -#### CWG 2518が適用されていない環境でのワークアラウンド +#### CWG 2518が適用されていない環境での回避策 言い換えれば`static_assert`に渡す条件式が依存名ならばテンプレートの宣言時に検証されず、テンプレート実体化まで評価を遅らせることができる。 From 828812e7017b53318eaf43e282952acec9fc6313 Mon Sep 17 00:00:00 2001 From: yumetodo Date: Thu, 21 Mar 2024 02:54:26 +0900 Subject: [PATCH 3/7] =?UTF-8?q?fix:=20=E5=AE=A3=E8=A8=80=E3=81=AE=E9=81=85?= =?UTF-8?q?=E5=BB=B6=E3=81=8C=E3=81=95=E3=82=8C=E3=82=8B=E3=81=AE=E3=81=A7?= =?UTF-8?q?=E3=81=AF=E3=81=AA=E3=81=8F=E5=8D=98=E3=81=AB=E5=A4=B1=E6=95=97?= =?UTF-8?q?=E3=82=92=E7=84=A1=E8=A6=96=E3=81=99=E3=82=8B=E3=81=A8=E3=81=84?= =?UTF-8?q?=E3=81=86=E4=BB=95=E6=A7=98=E3=81=A7=E3=81=82=E3=81=A3=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ref: - https://timsong-cpp.github.io/cppwp/n4950/temp.res#general-6.1 - https://github.com/cpprefjp/site/pull/1260#discussion_r1527429722 --- lang/cpp17/if_constexpr.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lang/cpp17/if_constexpr.md b/lang/cpp17/if_constexpr.md index cb9330d5ab..ddc922d0d3 100644 --- a/lang/cpp17/if_constexpr.md +++ b/lang/cpp17/if_constexpr.md @@ -94,7 +94,7 @@ int main() ### `static_assert`文に関する例外 -後に述べる2段階名前探索に関する注意点とは関係なく、C++23以降、もしくはCWG 2518が適用された環境においては、template文(もしくは適切な特殊化や`constexpr if`文の中の文)が実際にインスタンス化されるまで、`static_assert`文の宣言は遅延される。 +後に述べる2段階名前探索に関する注意点とは関係なく、C++23以降、もしくはCWG 2518が適用された環境においては、template文(もしくは適切な特殊化や`constexpr if`文の中の文)が実際にインスタンス化されない限り、`static_assert`宣言による失敗は無視される。 ```cpp example #include @@ -109,8 +109,8 @@ void f(T t) { void g(std::int8_t c) { std::int32_t n = 0; - f(n); // OK: nはstd::int32_t型なので`use(t);`のほうがインスタンス化されるために、static_assert文は宣言されない。 - f(c); // error: cはstd::int8_t型なので、static_assert文は宣言され、"must be 32bit"とコンパイラが診断メッセージを出力する + f(n); // OK: nはstd::int32_t型なので`use(t);`のほうがインスタンス化されるために、static_assert宣言の失敗は無視される。 + f(c); // error: cはstd::int8_t型なので、static_assert宣言は失敗し、"must be 32bit"とコンパイラが診断メッセージを出力する } ``` From 56695bcea52c3fed29648859a43d8a9fd44ab9ae Mon Sep 17 00:00:00 2001 From: yumetodo Date: Thu, 21 Mar 2024 02:58:47 +0900 Subject: [PATCH 4/7] =?UTF-8?q?fix:=20static=5Fassert=E3=81=AF=E6=96=87?= =?UTF-8?q?=E3=81=98=E3=82=83=E3=81=AA=E3=81=8F=E3=81=A6=E5=AE=A3=E8=A8=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ref: - https://github.com/cpprefjp/site/pull/1260#discussion_r1527429722 --- lang/cpp17/if_constexpr.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lang/cpp17/if_constexpr.md b/lang/cpp17/if_constexpr.md index ddc922d0d3..19e057d3fa 100644 --- a/lang/cpp17/if_constexpr.md +++ b/lang/cpp17/if_constexpr.md @@ -92,7 +92,7 @@ int main() } ``` -### `static_assert`文に関する例外 +### `static_assert`宣言に関する例外 後に述べる2段階名前探索に関する注意点とは関係なく、C++23以降、もしくはCWG 2518が適用された環境においては、template文(もしくは適切な特殊化や`constexpr if`文の中の文)が実際にインスタンス化されない限り、`static_assert`宣言による失敗は無視される。 From 49d191be751d4295e3e67d3c62bb4a0f2c42cb12 Mon Sep 17 00:00:00 2001 From: yumetodo Date: Thu, 21 Mar 2024 03:00:10 +0900 Subject: [PATCH 5/7] =?UTF-8?q?fix:=20s/=E3=82=A4=E3=83=B3=E3=82=B9?= =?UTF-8?q?=E3=82=BF=E3=83=B3=E3=82=B9=E5=8C=96/=E5=AE=9F=E4=BD=93?= =?UTF-8?q?=E5=8C=96/?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lang/cpp17/if_constexpr.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lang/cpp17/if_constexpr.md b/lang/cpp17/if_constexpr.md index 19e057d3fa..088bd0b3c6 100644 --- a/lang/cpp17/if_constexpr.md +++ b/lang/cpp17/if_constexpr.md @@ -94,7 +94,7 @@ int main() ### `static_assert`宣言に関する例外 -後に述べる2段階名前探索に関する注意点とは関係なく、C++23以降、もしくはCWG 2518が適用された環境においては、template文(もしくは適切な特殊化や`constexpr if`文の中の文)が実際にインスタンス化されない限り、`static_assert`宣言による失敗は無視される。 +後に述べる2段階名前探索に関する注意点とは関係なく、C++23以降、もしくはCWG 2518が適用された環境においては、template文(もしくは適切な特殊化や`constexpr if`文の中の文)が実際に実体化されない限り、`static_assert`宣言による失敗は無視される。 ```cpp example #include @@ -109,7 +109,7 @@ void f(T t) { void g(std::int8_t c) { std::int32_t n = 0; - f(n); // OK: nはstd::int32_t型なので`use(t);`のほうがインスタンス化されるために、static_assert宣言の失敗は無視される。 + f(n); // OK: nはstd::int32_t型なので`use(t);`のほうが実体化されるために、static_assert宣言の失敗は無視される。 f(c); // error: cはstd::int8_t型なので、static_assert宣言は失敗し、"must be 32bit"とコンパイラが診断メッセージを出力する } ``` From 3da7c5d7099a1ece52486c38d2f6fc7599940fda Mon Sep 17 00:00:00 2001 From: yumetodo Date: Mon, 8 Apr 2024 23:11:27 +0900 Subject: [PATCH 6/7] =?UTF-8?q?fix:=20INFERIOR=5FSTATIC=5FASSERT=E3=81=AF?= =?UTF-8?q?=E3=82=84=E3=81=A3=E3=81=B1=E3=82=8A=E6=8C=81=E3=81=A1=E5=87=BA?= =?UTF-8?q?=E3=81=95=E3=81=9A=E3=81=AB=E3=82=B5=E3=83=A9=E3=83=83=E3=81=A8?= =?UTF-8?q?=E6=B5=81=E3=81=99=20(#1260)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lang/cpp17/if_constexpr.md | 28 ++-------------------------- 1 file changed, 2 insertions(+), 26 deletions(-) diff --git a/lang/cpp17/if_constexpr.md b/lang/cpp17/if_constexpr.md index 088bd0b3c6..b9a3814852 100644 --- a/lang/cpp17/if_constexpr.md +++ b/lang/cpp17/if_constexpr.md @@ -116,33 +116,9 @@ void g(std::int8_t c) { ### 2段階名前探索における注意点 -`constexpr if`文で、実行されない方の`statement`は廃棄文(discarded statement)となり、文の実体化を防ぐ。言い換えると、2段階名前探索における依存名(dependent name)は、廃棄文の場合検証されない。また文が実体化されないのだから通常のif文と同じくもちろん実行時に実行もされない。つまり次の例は意図と異なる挙動を示す。 +`constexpr if`文で、実行されない方の`statement`は廃棄文(discarded statement)となり、文の実体化を防ぐ。言い換えると、2段階名前探索における依存名(dependent name)は、廃棄文の場合検証されない。また文が実体化されないのだから通常のif文と同じくもちろん実行時に実行もされない。 -```cpp -template struct inferior_static_assert_failure; -template<> struct inferior_static_assert_failure{ enum { value = 1 }; }; -#define INFERIOR_STATIC_ASSERT(B) typedef char inferior_static_assert[sizeof(inferior_static_assert_failure::value)] -#include - -template -void f(T) -{ - if constexpr (std::is_same_v) - { - // Tがintのときのみ評価されてほしい - // 実際は常に評価される - INFERIOR_STATIC_ASSERT(false); - } -} - -int main() -{ - f(2.4); - f(3); -} -``` - -CWG 2518が適用されていない環境においては`static_assert`でも同じ現象が発生する。 +CWG 2518が適用されていない環境においては`static_assert`を用いる時に直感的ではない挙動を示していた。 ```cpp example #include From ffb8d642373bbd16679718e8c0187b7262e9714c Mon Sep 17 00:00:00 2001 From: yumetodo Date: Mon, 8 Apr 2024 23:22:37 +0900 Subject: [PATCH 7/7] =?UTF-8?q?fix:=20lambda=E5=BC=8F=E3=82=92=E7=94=A8?= =?UTF-8?q?=E3=81=84=E3=81=9Fcgw2518=E5=9B=9E=E9=81=BF=E3=82=92=E9=99=A4?= =?UTF-8?q?=E5=8E=BB=20(#1260)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 編集方針「正確なものに不要な情報はない」はあるものの、全体の構成として必要性を感じなくなったので除去する。 内容そのものをうまく当てはまる形で復活させられるならばそれに異議を唱えるものではないが、いい方法が浮かばなかった。 ref: - https://github.com/cpprefjp/site/pull/1260#discussion_r1529830307 --- lang/cpp17/if_constexpr.md | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/lang/cpp17/if_constexpr.md b/lang/cpp17/if_constexpr.md index b9a3814852..c879b7611e 100644 --- a/lang/cpp17/if_constexpr.md +++ b/lang/cpp17/if_constexpr.md @@ -170,28 +170,6 @@ int main() } ``` -上の例では`false_v`を作ったが、ラムダ式でも同様のことができる。ラムダ式はそれが記述された位置から見て最小のスコープ (ブロックスコープ/クラススコープ/名前空間スコープ) で宣言されるクラスとして扱われる。例えば、下の例では`f()`という関数テンプレート内で宣言される。関数テンプレート内のクラスは依存名になるため、テンプレートの宣言時に検証されず、テンプレート実体化まで評価を遅らせることができる。 - -```cpp example -#include - -template -void f(T) -{ - if constexpr (std::is_same_v) - { - // Tがintのときのみ評価される - static_assert([]{return false;}()); - } -} - -int main() -{ - f(2.4); - f(3); -} -``` - ### 類似機能との比較 `constexpr if`文の導入によってC++の`if`系の条件分岐は3種類になった。