Предполагалось, что перегрузки операторов для пользовательских классов будут работать через поиск, зависящий от аргумента. 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.
person
bames53
schedule
13.02.2014
using namespace std::rel_ops
заключается в том, что операторы не учитываются при поиске, зависящем от аргументов. Это означает, что, например,std::greater<my_type>
не удастся скомпилировать (в то время как он был бы успешным, если бы подходящийoperator>
был определен в том же пространстве имен, что иmy_type
, или в глобальном пространстве имен). - person Mike Seymour   schedule 03.06.2011<=>
. - person L. F.   schedule 06.06.2019