При наличии пары программ с using namespace std::rel_ops;
и без него, которые не нарушают правило, требующее диагностики c, различие в поведении может быть связано только с разрешением перегрузки между элементом rel_ops
и другой функцией или шаблон функции, который в некотором контексте является перегрузкой хуже с обоими объявлениями жизнеспособными. Контекст разрешения перегрузки может быть следующим:
- двоичное выражение сравнения, например
E1 != E2
- явный вызов с использованием operator-function-id в качестве имени функции Например,
operator!=(E1, E2)
- использование идентификатор-оператора-функции или
&
идентификатор-оператора-функции в качестве инициализатора для указателя на функцию или ссылку функционировать в одном из контекстов, перечисленных в [over.over] / 1 , например static_cast<bool(*)(const std::string&, const std::string&)>
На самом деле для другой функции или специализации шаблона функции не так уж сложно быть перегрузкой хуже, чем член std::rel_ops
. Примеры включают в себя:
Другая специализация функции или шаблона требует преобразования, определенного пользователем.
class A {};
bool operator==(const A&, const A&);
class B {
public:
B(A);
};
bool operator==(const B&, const B&);
bool operator!=(const B&, const B&);
void test1() {
// With using-directive, selects std::rel_ops::operator!=<A>(const A&, const A&).
// Without, selects operator!=(const B&, const B&).
A{} != A{};
}
class C {
operator int() const;
};
bool operator==(const C&, const C&);
void test2() {
// With using-directive, selects std::rel_ops::operator!=<C>.
// Without, selects the built-in != via converting both arguments to int.
C{} != C{};
Другая специализация функции или шаблона требует "преобразования в производную базу" (* 1031) * [over.best.ics] / 6 ).
class D {};
bool operator==(const D&, const D&);
bool operator!=(const D&, const D&);
class E : public D {};
bool operator==(const E&, const E&);
void test3() {
// With using-directive, selects std::rel_ops::operator!=<E>.
// Without, selects operator!=(const D&, const D&).
E{} != E{};
}
Другая специализация функции или шаблона имеет тип справочного параметра rvalue.
class F {};
bool operator==(F&&, F&&);
void test4() {
// With using-directive, selects std::rel_ops::operator!=<F>.
// Without, selects operator!=(F&&, F&&).
F{} != F{};
}
Другая функция специализация менее специализированного шаблона функции.
namespace N1 {
class A{};
bool operator==(const A&, const A&);
template <typename T1, typename T2>
bool operator!=(const T1&, const T2&);
}
void test5() {
// With using-directive, selects std::rel_ops::operator!=<N1::A>.
// Without, selects N1::operator!=<N1::A,N1::A>.
N1::A{} != N1::A{};
}
namespace N2 {
class B{};
bool operator==(const B&, const B&);
template <typename T>
bool operator!=(T&, T&);
}
void test6() {
// With using-directive, selects std::rel_ops::operator!=<N2::B>.
// Without, selects operator!=<const N2::B>.
const N2::B b1;
const N2::B b2;
b1 != b2;
}
Возможны и другие категории и примеры, но это более чем важно.
Что касается практических вопросов, то вряд ли имена операторов сравнения, объявленные в std::rel_ops
, будут реализованы для получения результатов, сильно отличающихся от определения rel_ops
для того же типа, учитывая, что также определены соответствующие operator<
или operator==
. Это может иметь значение, если «недопустимые» или специальные значения имеют специальную обработку, например, как для типов с плавающей запятой a <= b
не эквивалентно !(b < a)
, когда хотя бы один операнд является значением NaN. Но случаи, включающие неявное преобразование в другой тип, могут довольно легко привести к другому поведению. После преобразования в производную базу любая информация в элементах производных данных, скорее всего, будет игнорироваться. После конструктора преобразования или функции преобразования сравниваются значения совершенно другого типа, которые предположительно каким-то образом представляют исходные аргументы, но могут не представлять их полную функциональную идентичность.
(Поэтому для исходной мотивации я решил Стоит использовать инструмент анализа stati c, чтобы найти все места в существующем коде с именем члена std::rel_ops
, чтобы помочь проверить непреднамеренные изменения в значении, которые не обнаруживаются при простой компиляции.)