Ответ 1
Я думаю, что предпочтительный метод - не использовать std::rel_ops
при
все. Техника, используемая в boost::operator
(
Каков предпочтительный метод использования std::rel_ops
для добавления полного набора реляционных операторов в класс?
В этой документации предлагается using namespace std::rel_ops
, но это, кажется, глубоко ошибочно, поскольку это будет означать, что включая заголовок для класса реализованный таким образом, также добавит полные реляционные операторы ко всем другим классам с определенными operator<
и operator==
, даже если это нежелательно. Это может неожиданно изменить смысл кода.
В качестве побочного примечания - я использовал Boost.Operators, но мне все еще интересно узнать о стандартной библиотеке.
Я думаю, что предпочтительный метод - не использовать std::rel_ops
при
все. Техника, используемая в boost::operator
(
То, как перегрузка операторов для классов, определенных пользователем, предназначалась для работы, зависит от поиска зависимого от аргумента. ADL позволяет программам и библиотекам избегать загромождения глобального пространства имен с перегрузками операторов, но все же позволяет удобно использовать операторы; То есть без явной квалификации пространства имен, что невозможно сделать с синтаксисом оператора infix a + b
, и вместо этого потребовался бы обычный синтаксис функции your_namespace::operator+ (a, b)
.
ADL, однако, не просто ищет везде для любой возможной перегрузки оператора. ADL ограничивается просмотром только "связанных" классов и пространств имен. Проблема с std::rel_ops
заключается в том, что, как указано, это пространство имен никогда не может быть ассоциированным пространством имен любого класса, определенного вне стандартной библиотеки, и поэтому ADL не может работать с такими определенными пользователем типами.
Однако, если вы хотите обмануть, вы можете сделать std::rel_ops
работу.
Связанные пространства имен определены в С++ 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
самостоятельно технически заставляет программу иметь поведение undefined, потому что С++ не позволяет пользовательским программам добавлять объявления к std
. В качестве примера того, как это действительно имеет значение, и почему, если вы это сделаете, вы можете столкнуться с проблемой проверки того, что ваша реализация действительно работает с этим дополнением, подумайте:
Выше показано объявление make_rel_ops_work
, которое следует за #include <utility>
. Можно было бы наивно ожидать, что включение здесь здесь не имеет значения и что до тех пор, пока заголовок будет включен когда-то до использования перегрузок оператора, ADL будет работать. Спецификация, конечно, не дает такой гарантии, и есть реальные реализации, когда это не так.
clang с libС++, из-за использования внутренних пространств имен libС++, будет (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, а не задано С++, поэтому вы даже не можете полагаться на это в других реализациях. В частности, gcc не ведет себя так, но, к счастью, libstdС++ не использует встроенные пространства имен. Если вы не хотите полагаться на это расширение, то для clang/libС++ вы можете написать:
#include <__config>
_LIBCPP_BEGIN_NAMESPACE_STD
namespace rel_ops { struct make_rel_ops_work {}; }
_LIBCPP_END_NAMESPACE_STD
но, очевидно, вам понадобятся реализации для других библиотек, которые вы используете. Мое упрощенное объявление make_rel_ops_work
работает для clang3.2/libС++, gcc4.7.3/libstdС++ и VS2012.
Это не самое приятное, но вы можете использовать using namespace std::rel_ops
как деталь реализации для реализации операторов сравнения на вашем типе. Например:
template <typename T>
struct MyType
{
T value;
friend bool operator<(MyType const& lhs, MyType const& rhs)
{
// The type must define `operator<`; std::rel_ops doesn't do that
return lhs.value < rhs.value;
}
friend bool operator<=(MyType const& lhs, MyType const& rhs)
{
using namespace std::rel_ops;
return lhs.value <= rhs.value;
}
// ... all the other comparison operators
};
Используя using namespace std::rel_ops;
, мы разрешаем ADL искать operator<=
, если он определен для типа, но в противном случае опуститься на тот, который определен в std::rel_ops
.
Это все равно боль, хотя вам все равно придется писать функцию для каждого из операторов сравнения.
Проблема с добавлением пространства имен rel_ops, независимо от того, выполняете ли вы это с помощью руководства using namespace rel_ops;
или выполняете ли вы это автоматически, как описано в ответе @bames53, заключается в том, что добавление пространства имен может иметь непредвиденные побочные эффекты на участках ваш код. Я нашел это сам совсем недавно, поскольку некоторое время я использовал решение @bames53, но когда я сменил одну из своих операций на основе контейнера, чтобы использовать обратный_запись вместо итератора (внутри мультимапа, но я подозреваю, что это будет одинаково для любой из стандартных контейнеров), внезапно я получал ошибки компиляции при использовании!= для сравнения двух итераторов. В конечном счете я отследил его до того, что код включал пространство имен rel_ops, которое мешало тому, как определяются обратные_тераторы.
Использование boost было бы способом его решения, но, как упоминалось в @Tom, не все готовы использовать boost, я сам включил. Поэтому я применил свой собственный класс для решения проблемы, и я подозреваю, что это тоже самое, но я не проверял библиотеки boost.
В частности, я определил следующую структуру:
template <class T>
struct add_rel_ops {
inline bool operator!=(const T& t) const noexcept {
const T* self = static_cast<const T*>(this);
return !(*self == t);
}
inline bool operator<=(const T& t) const noexcept {
const T* self = static_cast<const T*>(this);
return (*self < t || *self == t);
}
inline bool operator>(const T& t) const noexcept {
const T* self = static_cast<const T*>(this);
return (!(*self == t) && !(*self < t));
}
inline bool operator>=(const T& t) const noexcept {
const T* self = static_cast<const T*>(this);
return !(*self < t);
}
};
Чтобы использовать это, когда вы определяете свой класс, скажите MyClass, вы можете наследовать его, чтобы добавить "отсутствующих" операторов. Конечно, вам нужно определить == и < операторов в MyClass (не показано ниже).
class MyClass : public add_rel_ops<MyClass> {
...stuff...
};
Важно, чтобы вы включили MyClass
в качестве аргумента шаблона. Если бы вы включили другой класс, скажем MyOtherClass
, static_cast
почти наверняка даст вам проблемы.
Обратите внимание, что мое решение предполагает, что операторы ==
и <
определены как const noexcept
, что является одним из требований моих личных стандартов кодирования. Если ваши стандарты разные, вам необходимо соответствующим образом изменить add_rel_ops.
Кроме того, если вас беспокоит использование static_cast
, вы можете изменить их как dynamic_cast
, добавив
virtual ~add_rel_ops() noexcept = default;
для класса add_rel_ops, чтобы сделать его виртуальным классом. Конечно, это также заставит MyClass
быть виртуальным классом, поэтому я не принимаю этот подход.