Способ перегрузки операторов для пользовательских классов должен был работать через поиск, зависящий от аргумента. ADL позволяет программам и библиотекам избегать загромождения глобального пространства имен перегрузками операторов, но все же позволяет удобно использовать операторы; То есть без явной квалификации пространства имен, что невозможно сделать с синтаксисом инфиксного оператора a + b
и вместо этого потребовался бы нормальный синтаксис функции your_namespace::operator+ (a, b)
.
ADL, однако, не просто ищет повсюду возможные перегрузки операторов. ADL ограничен, чтобы смотреть только на «связанные» классы и пространства имен. Проблема с std::rel_ops
заключается в том, что, как указано, это пространство имен никогда не может быть связанным пространством имен какого-либо класса, определенного за пределами стандартной библиотеки, и поэтому ADL не может работать с такими пользовательскими типами.
Однако, если вы хотите обмануть, вы можете заставить std::rel_ops
работать.
Связанные пространства имен определены в C ++ 11 3.4.2 [basic.lookup.argdep] / 2. Для наших целей важным фактом является то, что пространство имен, членом которого является базовый класс, является связанным пространством имен наследующего класса, и, таким образом, ADL проверит эти пространства имен на наличие соответствующих функций.
Итак, если следующее:
#include <utility> // rel_ops
namespace std { namespace rel_ops { struct make_rel_ops_work {}; } }
должны были (каким-то образом) найти свой путь в модуле перевода, затем в поддерживаемых реализациях (см. Следующий раздел) вы могли бы затем определить свои собственные типы классов следующим образом:
namespace N {
// inherit from make_rel_ops_work so that std::rel_ops is an associated namespace for ADL
struct S : private std::rel_ops::make_rel_ops_work {};
bool operator== (S const &lhs, S const &rhs) { return true; }
bool operator< (S const &lhs, S const &rhs) { return false; }
}
И тогда ADL будет работать для вашего типа класса и найдет операторы в std::rel_ops
.
#include "S.h"
#include <functional> // greater
int main()
{
N::S a, b;
a >= b; // okay
std::greater<N::s>()(a, b); // okay
}
Конечно, добавление make_rel_ops_work
самостоятельно приводит к неопределенному поведению программы, поскольку C ++ не позволяет пользовательским программам добавлять объявления в std
. В качестве примера того, как это на самом деле имеет значение и почему, если вы это сделаете, вы можете попытаться проверить, действительно ли ваша реализация действительно работает с этим дополнением, рассмотрим:
Выше я показываю объявление make_rel_ops_work
, следующее за #include <utility>
. Можно наивно ожидать, что включение этого здесь не имеет значения и что, если заголовок включен иногда до использования перегрузок оператора, ADL будет работать. Спецификация, конечно, не дает такой гарантии, и есть реальные реализации, где это не так.
clang с libc ++, из-за использования libc ++ встроенных пространств имен, (IIUC) будет считать, что объявление make_rel_ops_work
находится в отличном пространстве имен от пространства имен, содержащего перегрузки оператора <utility>
, если только объявление <utility>
std::rel_ops
на первом месте. Это связано с тем, что технически std::__1::rel_ops
и std::rel_ops
являются отдельными пространствами имен, даже если std::__1
является встроенным пространством имен. Но если clang видит, что исходное объявление пространства имен для rel_ops
находится во встроенном пространстве имен __1
, то оно будет обрабатывать объявление namespace std { namespace rel_ops {
как расширение std::__1::rel_ops
, а не как новое пространство имен.
Я полагаю, что это поведение расширения пространства имен является расширением clang, а не определено в C ++, поэтому вы даже не сможете полагаться на это в других реализациях. В частности, gcc не ведет себя таким образом, но, к счастью, libstdc ++ не использует встроенные пространства имен. Если вы не хотите полагаться на это расширение, тогда для clang / libc ++ вы можете написать:
#include <__config>
_LIBCPP_BEGIN_NAMESPACE_STD
namespace rel_ops { struct make_rel_ops_work {}; }
_LIBCPP_END_NAMESPACE_STD
но, очевидно, тогда вам понадобятся реализации для других библиотек, которые вы используете. Мое более простое объявление make_rel_ops_work
работает для clang3.2 / libc ++, gcc4.7.3 / libstdc ++ и VS2012.