Итак, я хочу написать автоматический !=
:
template<typename U, typename T>
bool operator!=(U&& u, T&& t) {
return !( std::forward<U>(u) == std::forward<T>(t) );
}
но это невежливо 1. Поэтому я пишу
// T() == U() is valid?
template<typename T, typename U, typename=void>
struct can_equal:std::false_type {};
template<typename T, typename U>
struct can_equal<
T,
U,
typename std::enable_if<
std::is_convertible<
decltype( std::declval<T>() == std::declval<U>() ),
bool
>::value
>::type
>: std::true_type {};
который представляет собой класс признаков типов, который говорит: "is t == u
допустимый код, который возвращает тип, конвертируемый в bool
".
Итак, я улучшаю свой !=
:
template<typename U, typename T,
typename=typename std::enable_if<can_equal<T,U>::value>::type
>
bool operator!=(U&& u, T&& t) {
return !( std::forward<U>(u) == std::forward<T>(t) );
}
и теперь это только допустимое переопределение, если ==
существует. К сожалению, это немного жадный:
struct test {
};
bool operator==(const test&, const test&);
bool operator!=(const test&, const test&);
так как он будет хватать почти все test() != test()
, а не вызывается выше !=
. Я думаю, что это нежелательно - я бы скорее назвал явный !=
, чем auto-forward, на ==
и отрицал.
Итак, я пишу этот класс признаков:
template<typename T, typename U,typename=void>
struct can_not_equal // ... basically the same as can_equal, omitted
который проверяет, действительно ли T != U
.
Затем мы увеличиваем !=
следующим образом:
template<typename U, typename T,
typename=typename std::enable_if<
can_equal<T,U>::value
&& !can_not_equal<T,U>::value
>::type
>
bool operator!=(U&& u, T&& t) {
return !( std::forward<U>(u) == std::forward<T>(t) );
}
который, если вы его разобрали, говорит "это предложение ложно" - operator!=
существует между T
и U
iff operator!=
не существует между T
и U
.
Неудивительно, что каждый компилятор я тестировал segfaults, когда его кормили. (clang 3.2, gcc 4.8 4.7.2 intel 13.0.1). Я подозреваю, что то, что я делаю, является незаконным, но мне бы хотелось увидеть стандартную ссылку. (edit: то, что я делаю, является незаконным, поскольку оно вызывает неограниченное рекурсивное расширение шаблона, если мой !=
применяется, мы должны проверить, применим ли мой !=
. Версия, связанная в комментариях с #if 1
, дает разумную ошибку).
Но мой вопрос: есть ли способ убедить моего основанного на SFINAE переопределения игнорировать "себя" при принятии решения о том, должен ли он потерпеть неудачу или нет или каким-то образом избавиться от самореференциальной проблемы? Или понизить приоритет моего operator!=
до минимума, чтобы выиграть какой-либо явный !=
, даже если это не так хорошо соответствует?
Тот, который не проверяет, что "!=
не существует" работает достаточно хорошо, но недостаточно для меня, чтобы быть таким же невежливым, как внедрить его в глобальное пространство имен.
Цель - любой код, который будет компилироваться без моего "волшебного" !=
, делает то же самое, как только будет введено мое "волшебное" !=
. Если и только если !=
в противном случае недействителен и bool r = !(a==b)
хорошо сформирован, если мой "волшебный" !=
ударит.
Сноска 1: если вы создаете template<typename U, typename T> bool operator!=(U&& u, T&& t)
, SFINAE будет думать, что каждая пара типов имеет действительный !=
между ними. Затем, когда вы пытаетесь на самом деле вызвать !=
, он создается и не компилируется. Кроме того, вы топаете в функциях bool operator!=( const foo&, const foo& )
, потому что вы лучше сочетаетесь для foo() != foo()
и foo a, b; a != b;
. Я считаю, что оба эти невежливые.