Идиоматическое использование std:: rel ops
каков предпочтительный метод использования std::rel_ops
добавить полный набор реляционных операторов в класс?
этой документация предполагает using namespace std::rel_ops
, но это кажется глубоко ошибочным, так как это означало бы, что включение заголовка для класса, реализованного таким образом, также добавит полные реляционные операторы ко всем другим классам с определенным operator<
и operator==
, даже если это было нежелательно. Это может изменить значение кода в удивительный способ.
в качестве примечания-я использовал импульс.Операторы чтобы сделать это, но мне все еще интересно о стандартной библиотеке.
4 ответов
Я думаю, что выбранный метод не использовать std::rel_ops
at
все. Техника, используемая в boost::operator
(ссылке) кажется, как обычно
решение.
пример:
#include "boost/operators.hpp"
class SomeClass : private boost::equivalent<SomeClass>, boost::totally_ordered<SomeClass>
{
public:
bool operator<(const SomeClass &rhs) const
{
return someNumber < rhs.someNumber;
}
private:
int someNumber;
};
int main()
{
SomeClass a, b;
a < b;
a > b;
a <= b;
a >= b;
a == b;
a != b;
}
перегрузка оператора для пользовательских классов должна была работать через поиск, зависящий от аргумента. ADL позволяет программам и библиотекам избегать загромождения глобального пространства имен перегрузками операторов, но все же позволяет удобно использовать операторы; то есть без явной квалификации пространства имен, что невозможно сделать с синтаксисом оператора infix a + b
и вместо этого потребуется обычный синтаксис функции your_namespace::operator+ (a, b)
.
ADL, однако, не просто ищет везде для любой возможной перегрузки оператора. ADL ограничен для просмотра только "связанных" классов и пространств имен. Проблема с std::rel_ops
это, как указано, это пространство имен Никогда не может быть связанным пространством имен любого класса, определенного вне стандартной библиотеки, и поэтому ADL не может работать с такими пользовательскими типами.
однако, если вы готовы обмануть вы можете сделать std::rel_ops
работа.
связанные пространства имен, определенные в C++11 3.4.2 [основной.уважать.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.
это не самое приятное, но вы можете использовать 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 в течение некоторого времени, но когда я изменил одну из моих операций на основе контейнера, чтобы использовать reverse_iterator вместо итератора (в multimap, но я подозреваю, что это будет то же самое для любого из стандартных контейнеров), вдруг я получаю ошибки компиляции при использовании != для сравнения двух итераторов. В конечном итоге я отследил его до того, что код включал пространство имен rel_ops, которое мешало определению reverse_iterators.
использование boost было бы способом решить эту проблему, но, как упоминалось @Tom, не все готовы использовать boost, включая меня. Поэтому я реализовал свой собственный класс для решения проблемы, которую я подозреваю также, как 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, вы можете наследовать от этого, чтобы добавить "недостающие" операторов. Конечно, вам нужно определить операторы == и
class MyClass : public add_rel_ops<MyClass> {
...stuff...
};
важно, чтобы вы включили MyClass
в качестве аргумента шаблона. Если бы вы включили другой класс, скажем MyOtherClass
, the static_cast
было бы почти наверняка дать вам проблемы.
обратите внимание, что мое решение предполагает, что ==
и <
операторы определяются как const noexcept
что является одним из требований моих личных стандартов кодирования. Если ваши стандарты отличаются, вам нужно будет соответствующим образом изменить add_rel_ops.
кроме того, если вас беспокоит использование static_cast
, вы можете изменить их, чтобы быть dynamic_cast
by добавление
virtual ~add_rel_ops() noexcept = default;
в класс add_rel_ops, чтобы сделать его виртуальным классом. Конечно, это тоже заставит MyClass
быть виртуальным классом, поэтому я не использую этот подход.