C++20コンセプトと論理演算子(&&
, ||
)による畳み込み式(fold expression)の関係について。本記事の内容はStackOverflowで見つけた質問と回答に基づく。
まとめ:&&
と||
による畳み込み式を用いた制約式(constraint-expression)は機能するものの、コンセプト間の包摂関係(subsumption relation)は期待通りに成り立たない。
2023-10-10追記:C++2c(C++26)に向けた提案P2963にて、&&
と||
畳み込み式を用いた場合でも包摂関係が成り立つよう言語仕様を調整する検討が行われている。
#include <concepts>
template <typename T, typename... Us>
concept any_of = (std::same_as<T, Us> || ...);
template <typename T>
constexpr int f(T) { return 1; }
template <typename T>
requires any_of<T, int, double>
constexpr int f(T) { return 2; }
template <any_of<int, double> T>
constexpr int f(T) { return 2; }
static_assert(f(42) == 2);
static_assert(f(3.14) == 2);
static_assert(f(0.f) == 1);
static_assert(f('X') == 1);
C++20コンセプト仕様では、畳み込み式(std::same_as<T, Us> || ...)
それ自体で一つの原始制約(atomic constraint)を構成する。オーバーロード解決のために制約式の包摂関係を求める(≒強弱の判定)正規化(normalization)過程で、std::same_as<T, Us[0]>
⋁std::same_as<T, Us[1]>
⋁...
のようには展開解釈されない。*1
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; }
template <std::same_as<double> T>
constexpr int f(T) { return 3; }
static_assert(f(42) == 2);
static_assert(f(3.14) == 3);
可変長コンセプトをあきらめて制約式(std::same_as<T, U1> || std::same_as<T, U2>)
とすれば包摂関係が成立し、自然なオーバーロード選択が行われる。つまり制約any_of2<T, int, double>
よりも制約std::same_as<T, double>
の方がより強い制約(more constrained)と解釈される。
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; }
template <std::same_as<double> T>
constexpr int f(T) { return 3; }
static_assert(f(42) == 2);
static_assert(f(3.14) == 3);
関連URL