Избегайте взаимодополняющих ограничений
Причина
Ясность. Удобство сопровождения. Функции со взаимодополняющими требованиями, выраженными через отрицание, ненадёжны.
Пример
Поначалу люди пытаются определять функции со взаимодополняющими требованиями:
template<typename T>
requires !C<T> // плохо
void f();
template<typename T>
requires C<T>
void f();
Лучше так:
template<typename T> // общий шаблон
void f();
template<typename T> // специализация через концепт
requires C<T>
void f();
Компилятор выберет неограниченный шаблон только тогда, когда C<T> не выполняется. Если вы не хотите (или не можете) определять неограниченную версию f(), удалите её.
template<typename T>
void f() = delete;
Компилятор выберет нужную перегрузку или выдаст соответствующую ошибку.
Примечание
К сожалению, взаимодополняющие ограничения широко распространены в коде с enable_if:
template<typename T>
enable_if<!C<T>, void> // плохо
f();
template<typename T>
enable_if<C<T>, void>
f();
Примечание
Взаимодополняющие требования для одного требования иногда (ошибочно) считаются приемлемыми. Однако для двух и более требований число необходимых определений может расти экспоненциально (2, 4, 8, 16, ...):
C1<T> && C2<T>
!C1<T> && C2<T>
C1<T> && !C2<T>
!C1<T> && !C2<T>
Теперь возможности для ошибок умножаются.
Контроль
- Помечать пары функций с ограничениями
C<T>и!C<T>