C++2a(C++20)コンセプトでは制約式(constraint-expression)に論理積(&&
)/論理和(||
)/論理否定(!
)を表現できるが、制約式を用いたオーバーロード解決では通常の論理演算で期待される ド・モルガンの法則(De Morgan's laws) は適用されない。*1
超要約:否定演算!
を使ったコンセプトには要注意!
// C++2a #include <concepts> // integral, same_asコンセプト template <typename T> requires (!std::integral<T>) void f(T) { /*...*/ } // #1 template <typename T> requires (!std::integral<T> && std::same_as<T, float>) void f(T) { /*...*/ } // #2 f(3.14 ); // OK: #1を選択(T=double) f(3.14f); // NG: オーバーロード解決が曖昧(T=float)
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)している。(」・ω・)」
// C++2a #include <concepts> template <typename T> concept not_integral = !std::integral<T>; template <typename T> requires not_integral<T> void f(T) { /*...*/ } // #1 template <typename T> requires not_integral<T> && std::same_as<T, float> void f(T) { /*...*/ } // #2 f(3.14 ); // OK: #1を選択(T=double) f(3.14f); // OK: #2を選択(T=float)
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); // ambiguous, !sad<T> atomic constraint expressions (13.5.1.2) // are not formed from the same expression 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); // OK, !sad<T> atomic constraint expressions both come from not_sad template <class T> int f3(T) requires (!sad<typename T::type>); int i3 = f3(42); // error: associated constraints not satisfied due to substitution failure 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); // OK, substitution failure contained within sad_nested_typeHere,
requires (!sad<typename T::type>)
requires that there is a nestedtype
that is notsad
, whereasrequires (!sad_nested_type<T>)
requires that there is nosad
nestedtype
. --end example] --end note]
関連URL
- P1971R0 Core Language Changes for NB Comments at the November, 2019 (Belfast) meeting, US111. Constraint normalization and negation
- c++ - Negation and De Morgan's Law not part of C++20 partial ordering by constraints - Stack Overflow
- コンセプト制約式の包摂関係とオーバーロード解決 - yohhoyの日記
- cpprefjp: コンセプト