Подтвердить что ты не робот

Можете ли вы создавать пользовательские операторы на С++?

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

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

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

4b9b3361

Ответ 1

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

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

Так как это не обычные пользовательские операторы, а просто трюки перегрузки оператора, есть несколько предостережений:

  • Макросы являются злыми. Если вы допустили ошибку, компилятор будет почти бесполезен для отслеживания проблемы.
  • Даже если вы получите макрос правильно, если есть ошибка в использовании вами оператора или в определении вашей операции, компилятор будет немного более полезен.
  • Вы должны использовать действительный идентификатор как часть оператора. Если вам нужен более символьный оператор, вы можете использовать _, 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 поддерживает постфиксные унарные операторы
  • Шаблоны IdOp используют ссылки, а не указатели, чтобы исключить использование бесплатного хранилища и дать возможность полной оценки времени выполнения операции
  • IdOp позволяет легко указать несколько операций для одного и того же корневого идентификатора

Ответ 2

В "Синтаксическом аспартаме" Сандера Стокса подробно рассмотрен метод, позволяющий использовать следующий формат:

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;
}

Ответ 3

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

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

Ответ 4

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

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

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

Ответ 5

Технически, нет. То есть вы не можете расширять набор 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? Это не может быть само выражение. Это не может быть имя типа, имя typedef или имя шаблона. Это не может быть имя функции. На самом деле это может быть только макрос, который является единственными оставшимися именованными объектами. Для этого см. Ответ "Да (ну, вид)".

Ответ 6

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

#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