C++20コンセプトと論理演算子(&&
, ||
)による畳み込み式(fold expression)の関係について。本記事の内容はStackOverflowで見つけた質問と回答に基づく。
まとめ:&&
と||
による畳み込み式を用いた制約式(constraint-expression)は機能するものの、コンセプト間の包摂関係(subsumption relation)は期待通りに成り立たない。
2023-10-10追記:C++2cに向けた提案P2963R0にて、&&
と||
畳み込み式を用いた場合でも包摂関係が成り立つよう言語仕様を調整する検討が行われている。
2024-07-21追記:C++2c(C++26)標準ライブラリでは提案文書(PDF)P2963R3が採択されたが*1、本記事のケースはサポートしない。畳み込み式が関与するより広範なケースは提案P2841にて継続議論されている。
#include <concepts> // コンセプト any_of<T, Us...> // 型Tが型リストUs..のいずれかと一致する? template <typename T, typename... Us> concept any_of = (std::same_as<T, Us> || ...); template <typename T> constexpr int f(T) { return 1; } // #1 template <typename T> requires any_of<T, int, double> constexpr int f(T) { return 2; } // #2 // または template <any_of<int, double> T> constexpr int f(T) { return 2; } // #2 static_assert(f(42) == 2); // OK: #2 int型 static_assert(f(3.14) == 2); // OK: #2 double型 static_assert(f(0.f) == 1); // OK: #1 float型 static_assert(f('X') == 1); // OK: #1 char型
C++20コンセプト仕様では、畳み込み式(std::same_as<T, Us> || ...)
それ自体で一つの原始制約(atomic constraint)を構成する。オーバーロード解決のために制約式の包摂関係を求める(≒強弱の判定)正規化(normalization)過程で、std::same_as<T, Us[0]>
⋁std::same_as<T, Us[1]>
⋁...
のようには展開解釈されない。*2
// 畳み込み式による(可変長)コンセプト定義 template <typename T, typename... Us> concept any_of = (std::same_as<T, Us> || ...); template <any_of<int, double> T> constexpr int f(T) { return 2; } // #2 template <std::same_as<double> T> constexpr int f(T) { return 3; } // #3 static_assert(f(42) == 2); // OK: #2の制約のみ満たす static_assert(f(3.14) == 3); // NG: #2,#3間でオーバロード解決が曖昧
可変長コンセプトをあきらめて制約式(std::same_as<T, U1> || std::same_as<T, U2>)
とすれば包摂関係が成立し、自然なオーバーロード選択が行われる。つまり制約any_of2<T, int, double>
よりも制約std::same_as<T, double>
の方がより強い制約(more constrained)と解釈される。
// 畳み込み式を利用しない(2型版)コンセプト定義 template <typename T, typename U1, typename U2> concept any_of2 = (std::same_as<T, U1> || std::same_as<T, U2>); template <any_of2<int, double> T> constexpr int f(T) { return 2; } // #2 template <std::same_as<double> T> constexpr int f(T) { return 3; } // #3 static_assert(f(42) == 2); // OK: #2 static_assert(f(3.14) == 3); // OK: #3は#2より強く制約される
関連URL
*1:https://github.com/cplusplus/papers/issues/1638
*2:C++20 §13.5.1.1/p1 Note: "(snip) For the purpose of exposition, conjunction is spelled using the symbol ⋀ and disjunction is spelled using the symbol ⋁. (snip)"