C++2a(C++20)コンセプトでは制約式(constraint-expression)に論理積(&&
)/論理和(||
)/論理否定(!
)を表現できるが、制約式を用いたオーバーロード解決では通常の論理演算で期待される ド・モルガンの法則(De Morgan's laws) は適用されない。*1
超要約:否定演算!
を使ったコンセプトには要注意!
#include <concepts>
template <typename T>
requires (!std::integral<T>)
void f(T) { }
template <typename T>
requires (!std::integral<T> && std::same_as<T, float>)
void f(T) { }
f(3.14 );
f(3.14f);
2番目の関数呼び出し(T=float
)では関数f
の#1, #2両オーバーロードとも制約式を満たすが、包摂関係(subsumption relation)は成り立たないためオーバーロード解決が曖昧となる。これは#1, #2の制約式に含まれる部分式!std::integral<T>
が、それぞれ異なる原始制約(atomic constraint)と解釈されるため。(「・ω・)「
後述例のように、コンセプトnot_integral
を介して “同一の式から構成される原始制約” とすれば期待通り動作する。ここでは#2の制約式not_integral<T> && std::same_as<T, float>
は#1の制約式not_integral<T>
を包摂(subsume)している。(」・ω・)」
#include <concepts>
template <typename T>
concept not_integral = !std::integral<T>;
template <typename T>
requires not_integral<T>
void f(T) { }
template <typename T>
requires not_integral<T> && std::same_as<T, float>
void f(T) { }
f(3.14 );
f(3.14f);
C++2a DIS n4861 13.5.1.1/p5より引用(下線部は強調)。注:13.7.6.1=Function template overloading, 13.5.4=Partial ordering by constraints
[Note: A logical negation expression (7.6.2.1) is an atomic constraint; the negation operator is not treated as a logical operation on constraints. As a result, distinct negation constraint-expressions that are equivalent under 13.7.6.1 do not subsume one another under 13.5.4. Furthermore, if substitution to determine whether an atomic constraint is satisfied (13.5.1.2) encounters a substitution failure, the constraint is not satisfied, regardless of the presence of a negation operator. [Example:
template <class T> concept sad = false;
template <class T> int f1(T) requires (!sad<T>);
template <class T> int f1(T) requires (!sad<T>) && true;
int i1 = f1(42);
template <class T> concept not_sad = !sad<T>;
template <class T> int f2(T) requires not_sad<T>;
template <class T> int f2(T) requires not_sad<T> && true;
int i2 = f2(42);
template <class T> int f3(T) requires (!sad<typename T::type>);
int i3 = f3(42);
template <class T> concept sad_nested_type = sad<typename T::type>;
template <class T> int f4(T) requires (!sad_nested_type<T>);
int i4 = f4(42);
Here, requires (!sad<typename T::type>)
requires that there is a nested type
that is not sad
, whereas requires (!sad_nested_type<T>)
requires that there is no sad
nested type
. --end example] --end note]
関連URL