Можете ли вы сделать пользовательские операторы в C ++? - PullRequest
38 голосов
/ 04 октября 2009

Можно ли сделать пользовательский оператор, чтобы вы могли делать такие вещи?

if ("Hello, world!" contains "Hello") ...

Примечание: это отдельный вопрос от "Это хорошая идея ...";)

Ответы [ 6 ]

31 голосов
/ 04 октября 2009

Да! (ну вроде)

Существует пара общедоступных инструментов, которые могут вам помочь. Оба используют генерацию кода препроцессора для создания шаблонов, которые реализуют пользовательские операторы. Эти операторы состоят из одного или нескольких встроенных операторов в сочетании с идентификатором.

Поскольку это на самом деле не пользовательские операторы, а всего лишь приемы перегрузки операторов, есть несколько предостережений:

  • Макросы - это зло. Если вы допустите ошибку, компилятор будет практически бесполезен для отслеживания проблемы.
  • Даже если вы правильно понимаете макрос, если в вашем использовании оператора или в определении вашей операции есть ошибка, компилятор будет лишь немного более полезным.
  • Вы должны использовать действительный идентификатор как часть оператора. Если вам нужен более символоподобный оператор, вы можете использовать _, o или аналогичные простые буквенно-цифровые символы.

CustomOperators

Пока я работал для этой цели над собственной библиотекой (см. Ниже), я наткнулся на этот проект. Вот пример создания оператора avg:

#define avg BinaryOperatorDefinition(_op_avg, /)
DeclareBinaryOperator(_op_avg)
DeclareOperatorLeftType(_op_avg, /, double);
inline double _op_avg(double l, double r)
{
   return (l + r) / 2;
}
BindBinaryOperator(double, _op_avg, /, double, double)

IdOp

То, что начиналось как упражнение с чистой легкомыслием , стало моим собственным решением этой проблемы. Вот похожий пример:

template<typename T> class AvgOp { 
public: 
   T operator()(const T& left, const T& right) 
   {
      return (left + right) / 2; 
   }
};
IDOP_CREATE_LEFT_HANDED(<, _avg_, >, AvgOp)
#define avg <_avg_>

Ключевые отличия

  • CustomOperators поддерживает унарные операторы postfix
  • В шаблонах IdOp используются ссылки, а не указатели, чтобы исключить использование бесплатного хранилища и обеспечить полную оценку операции во время компиляции
  • IdOp позволяет вам легко указать несколько операций для одного и того же корневого идентификатора
11 голосов
/ 10 мая 2015

Существует метод, тщательно изученный в 'Синтаксический аспартам' от Sander Stoks, который позволил бы вам использовать следующий формат:

if ("Hello, world!" <contains> "Hello") ...

По сути, вам нужен прокси-объект с перегруженными операторами '<' и '>'. Прокси выполняет всю работу; «содержит» может быть просто синглтоном без собственного поведения или собственных данных.

// Not my code!
const struct contains_ {} contains;

template <typename T>
struct ContainsProxy
{
    ContainsProxy(const T& t): t_(t) {}
    const T& t_;
};

template <typename T>
ContainsProxy<T> operator<(const T& lhs, const contains_& rhs)
{
    return ContainsProxy<T>(lhs);
}

bool operator>(const ContainsProxy<Rect>& lhs, const Rect& rhs)
{
    return lhs.t_.left   <= rhs.left && 
           lhs.t_.top    <= rhs.top && 
       lhs.t_.right  >= rhs.right && 
       lhs.t_.bottom >= rhs.bottom;
}
2 голосов
/ 04 октября 2009

Чтобы быть немного более точным, сам C ++ поддерживает только создание новых перегрузок существующих операций, но НЕ создание новых операторов. Существуют языки (например, ML и большинство его потомков), которые позволяют создавать совершенно новые операторы, но C ++ не является одним из них.

Судя по всему, (по крайней мере) библиотека CustomOperators, упомянутая в другом ответе, также не поддерживает полностью пользовательские операторы. По крайней мере, если я правильно читаю, это (внутренне) переводит ваш пользовательский оператор в перегрузку существующего оператора. Это упрощает работу за счет некоторой гибкости - например, когда вы создаете новый оператор в ML, вы можете дать ему приоритет, отличный от приоритета любого встроенного оператора.

0 голосов
/ 21 июня 2019

Я создал следующие два макроса:

#define define const struct
#define operator(ReturnType, OperatorName, FirstOperandType, SecondOperandType) OperatorName ## _ {} OperatorName; template <typename T> struct OperatorName ## Proxy{public:OperatorName ## Proxy(const T& t) : t_(t){}const T& t_;static ReturnType _ ## OperatorName ## _(const FirstOperandType a, const SecondOperandType b);};template <typename T> OperatorName ## Proxy<T> operator<(const T& lhs, const OperatorName ## _& rhs){return OperatorName ## Proxy<T>(lhs);}ReturnType operator>(const OperatorName ## Proxy<FirstOperandType>& lhs, const SecondOperandType& rhs){return OperatorName ## Proxy<FirstOperandType>::_ ## OperatorName ## _(lhs.t_, rhs);}template <typename T> inline ReturnType OperatorName ## Proxy<T>::_ ## OperatorName ## _(const FirstOperandType a, const SecondOperandType b)

Тогда вам просто нужно определить свой пользовательский оператор, как в следующем примере:

define operator(bool, myOr, bool, bool) { // Arguments are the return type, the name of the operator, the left operand type and the right operand type, respectively
    return a || b;
}

#define myOr <myOr> // Finally, you have to define a macro to avoid to put the < and > operator at the start and end of the operator name

Как только вы настроите своего оператора, вы можете использовать его в качестве предопределенного оператора:

bool a = true myOr false;
// a == true
0 голосов
/ 05 октября 2009

Технически, нет. То есть вы не можете расширить набор operator+, operator- и так далее. Но то, что вы предлагаете в своем примере, это нечто другое. Вы задаетесь вопросом, существует ли определение «содержит» такое, что string-literal "contains" string-literal является выражением с нетривиальной логикой (#define contains "" является тривиальным случаем).

Не так много выражений, которые могут иметь форму string-literal X string-literal. Это потому, что сами строковые литералы являются выражениями. Итак, вы ищете языковое правило вида expr X expr. Их довольно много, но все они являются правилами для операторов, и они не работают со строками. Несмотря на очевидную реализацию, "Hello, " + "world" не является допустимым выражением. Итак, что еще X может быть в string-literal X string-literal? Это не может быть само выражение. Это не может быть имя типа, имя определения типа или имя шаблона. Это не может быть именем функции. Это может быть только макрос, который является единственными именованными объектами. Для этого см. Ответ «Да (ну вроде)».

0 голосов
/ 04 октября 2009

Ваше предложение было бы не более чем синтаксическим сахаром для:

if( contains( "Hello, world!", "Hello" ) ...

и на самом деле уже есть функции для этого как в cstring, так и в std :: string. Что, возможно, немного похоже на ответ "это хорошая идея?" но не совсем; скорее спрашиваю "зачем тебе это нужно / хочешь?"

...