Avoid complementary constraints
Reason
Clarity. Maintainability. Functions with complementary requirements expressed using negation are brittle.
Example
Initially, people will try to define functions with complementary requirements:
template<typename T>
requires !C<T> // bad
void f();
template<typename T>
requires C<T>
void f();
This is better:
template<typename T> // general template
void f();
template<typename T> // specialization by concept
requires C<T>
void f();
The compiler will choose the unconstrained template only when C<T> is unsatisfied. If you do not want to (or cannot) define an unconstrained version of f(), then delete it.
template<typename T>
void f() = delete;
The compiler will select the overload, or emit an appropriate error.
Note
Complementary constraints are unfortunately common in enable_if code:
template<typename T>
enable_if<!C<T>, void> // bad
f();
template<typename T>
enable_if<C<T>, void>
f();
Note
Complementary requirements on one requirement are sometimes (wrongly) considered manageable. However, for two or more requirements the number of definitions needs can go up exponentially (2,4,8,16,...):
C1<T> && C2<T>
!C1<T> && C2<T>
C1<T> && !C2<T>
!C1<T> && !C2<T>
Now the opportunities for errors multiply.
Enforcement
- Flag pairs of functions with
C<T>and!C<T>constraints