Can you make custom operators in C++?

39,276

Solution 1

Yes! (well, sort of)

There are a couple publicly available tools to help you out. Both use preprocessor code generation to create templates which implement the custom operators. These operators consist of one or more built-in operators in conjunction with an identifier.

Since these aren't actually custom operators, but merely tricks of operator overloading, there are a few caveats:

  • Macros are evil. If you make a mistake, the compiler will be all but entirely useless for tracking down the problem.
  • Even if you get the macro right, if there is an error in your usage of the operator or in the definition of your operation, the compiler will be only slightly more helpful.
  • You must use a valid identifier as part of the operator. If you want a more symbol-like operator, you can use _, o or similarly simple alphanumerics.

CustomOperators

While I was working on my own library for this purpose (see below) I came across this project. Here is an example of creating an avg operator:

#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

What started as an exercise in pure frivolity became my own take on this problem. Here's a similar example:

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_>

Key Differences

  • CustomOperators supports postfix unary operators
  • IdOp templates use references rather than pointers to eliminate use of the free store, and to allow full compile-time evaluation of the operation
  • IdOp allows you to easily specify several operations for the same root identifier

Solution 2

There's a method thoroughly explored in 'Syntactic Aspartame' by Sander Stoks that would allow you to use the following format:

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

In essence, you need a proxy object with the operators '<' and '>' overloaded. The proxy does all of the work; 'contains' can just be a singleton with no behavior or data of its own.

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

Solution 3

I've created the following two macros:

#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)

Then, you'd have just to define your custom operator as in the following example:

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

Once a time you've set your operator up, you can use it as a predefined operator:

bool a = true myOr false;
// a == true

Warning

While this has been an interesting exercise, it merely demonstrates how bad is to have a macro–enabled precompiler. Adding custom operators like this can easily lead to a sort of metalanguage. Although we know how badly is C++ designed (most of all considering that it was first conceived as a set of extensions for C), we shouldn't be changing it. If you can't use standard C++, which is the only way to keep the code understandable by other people, you should just switch to another language that makes what you wish to do the way you'd like. There are thousands languages — no need to mess around with C++ to make it different.

SHORTLY: You just shouldn't be using this code. You should refrain from using macros unless when only used the same way as inline methods.

Solution 4

To be a bit more accurate, C++ itself only supports creating new overloads of existing operations, NOT creating new operators. There are languages (e.g., ML and most of its descendants) that do allow you to create entirely new operators, but C++ is not one of them.

From the looks of things, (at least) the CustomOperators library mentioned in the other answer doesn't support entirely custom operators either. At least if I'm reading things correctly, it's (internally) translating your custom operator into an overload of an existing operator. That makes things easier, at the expense of some flexibility -- for example, when you create a new operator in ML, you can give it precedence different from that of any built-in operator.

Solution 5

Technically, no. That is to say, you can't extend the set of operator+, operator-, etcetera. But what you're proposing in your example is something else. You are wondering if there is a definition of "contains" such that string-literal "contains" string-literal is an expression, with non-trivial logic (#define contains "" being the trivial case).

There are not many expressions that can have the form string-literal X string-literal. This is because string literals themselves are expressions. So, you're looking for a language rule of the form expr X expr. There are quite a few of those, but they're all rules for operators, and those don't work on strings. Despite the obvious implementation, "Hello, " + "world" is not a valid expression. So, what else can X be in string-literal X string-literal ? It can't be a expression itself. It can't be a typename, a typedef name or a template name. It can't be a function name. It can really only be a macro, which are the only remaining named entities. For that, see the "Yes (well, sort of)" answer.

Share:
39,276
Cogwheel
Author by

Cogwheel

You can also find me on LinkedIn: http://www.linkedin.com/in/matthewaorlando

Updated on August 03, 2021

Comments

  • Cogwheel
    Cogwheel almost 3 years

    Is it possible to make a custom operator so you can do things like this?

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

    Note: this is a separate question from "Is it a good idea to..." ;)