Can you make custom operators in C++?
Asked Answered
O

7

67

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..." ;)

Orthogenesis answered 4/10, 2009 at 2:4 Comment(0)
M
48

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
Mccusker answered 4/10, 2009 at 2:4 Comment(6)
One caveat: Since the pre-process stage happens before compilation, any error messages related to these custom operators have the potential to be VERY difficult to relate back to the code you wrote because the compile errors will be on whatever your code gets turned into. Not saying you shouldn't do it (if appropriate to your problem), but try to use sparingly - it's going to make your life difficult.Otiose
Sounds cool. Sounds clever. Something in the back of my head is telling me "You're doing it wrong" and "Custom operators were deliberately left out of the language spec."Dissipated
@Michael Kohne: Absolutely agree. I had some maddening debugging experiences over the last couple days.Orthogenesis
@Bob Kaufman: yeah, it's probably better as a novelty more than anything, but if it helps make something clearer in your code it might be a Good Thing TM.Orthogenesis
I'm guessing the ability to define new operators were left out of the language spec because it makes writing a C++ parser so much harder (and it's already pretty damn hard to begin with). You have to deal with operator precedence, associativity, etc.Elephus
@BobKaufman, if that were true, 80-90% of the current C++ spec would have been left out of the spec. Very obviously including leaving out inheritance and the class keyword.Nitrometer
S
23

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.

Saguenay answered 4/10, 2009 at 2:4 Comment(9)
This is gold like seriously this is amazing. Poor guy had to wait 9 years for this. He was ahead of his time.Oddity
While this is amazing (I never even believed something remotely like this was possible), I am encountering some difficulties. When I set an operator of scontains which takes two strings as LHS and RHS operands and returns a bool of a.find(b) != std::string::npos, it gives the error "cannot convert ‘std::string {aka std::basic_string}’ to ‘bool’ in initialization". What could be the reason for this, and is there a solution?Mesmerism
@mediocrevegetable1, it is hard to understand your issue without your full code. However, my code should work and indeed does for a lot of people, so I think it's your issue. However, this code shouldn't be ever used — I'm just adding an explanation about this in my answer.Saguenay
@DavideCannizzo Of course, I highly doubt I will use custom operators in a real-life situation; I was just playing around and seeing what all I could do with this, just because it was a cool thing. As for showing the code, I'm not sure where I really could put it (I don't think I can paste all my code into the comment section!), but if you do know a place then I would highly appreciate it.Mesmerism
@mediocrevegetable1, as for showing your code, you may want to ask your own question on Code Review, including the two macros in my answer that you've used to make your custom operator. Then, if you put a link to that question as a comment here, I can try to answer you.Saguenay
@DavideCannizzo Actually, just a litttle while ago I macro-ified John P's answer and that seemed to work for the same operator. Unfortunately, I don't think I have any more time today, but when I do, I'm gonna compare both macros to see what exactly my issue is before I post on Code review. I'll be sure to get back to you.Mesmerism
@DavideCannizzo Hello again, so sorry for taking so long, a lot of work came in the past week. Just a small update: I basically spread out the macro into multiple lines, tried it again, and it... works now. I'm not sure what exactly changed, maybe I missed copying a character before (though I don't think I did). I probably made some mistake I can't remember now. Once again, so sorry for wasting your time and taking so long to reply. What exactly I did wrong will forever remain a mystery, but at least your code works well, and I was indeed the one at fault.Mesmerism
@mediocrevegetable1, you're welcome. You didn't make me lose more than a few minutes, no need to be sorry for that ;) No matter how long you took, we're all busy :D — at least now it's been clarified. It's still possible that it was my mistake and you unawarely solved it while splitting the macro into multiple linesSaguenay
@DavideCannizzo Thanks a lot, that makes me feel better about things. I tried without spreading the macro out too afterwards, and that seemed to work too. I'm still a bit confused about what exactly happened, but yes, your macro works perfectly fine.Mesmerism
J
23

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;
}
Jezreel answered 4/10, 2009 at 2:4 Comment(2)
This article does a good job showing how the two libraries in my answer work.Orthogenesis
I think this is the way boost spirit does it.Gottuard
B
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.

Burgle answered 4/10, 2009 at 2:4 Comment(1)
I've added a clarification/caveat to my original answer. Thanks :)Orthogenesis
A
2

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.

Abstractionist answered 4/10, 2009 at 2:4 Comment(3)
I don't know what "extend" means in this context, but you definitely can define + and - operators in C++.Subinfeudation
@Andy: Obviously. You can also add overloads for operator*. What you cannot do is add operator@. The C++ standard fully specifies which operators exist, and only those can be overloaded with new types of arguments.Abstractionist
oh now I got what you meant previously. Yes, you cannot define your own custom operators.Subinfeudation
N
0

As others have pointed out you sadly can not write custom operators but with macros you can get similar behaviour. It is actually really easy with c style casting see below.


class To_Range{
public:
    size_t start;
    size_t end;
    To_Range(size_t _start,size_t _end) :
    start(_start), end(_end) {}

};

class Slicing_To_End{
public:
    int end;
    Slicing_To_End(const int& init) : end(init) {}
};

To_Range operator == (const int& start,const Slicing_To_End& end) {
    return To_Range(start,end.end);
}

#define to == (Slicing_To_End)

Here 4 to 5 will give back an object of type To_Range. (Slicing_To_End) casts 5 to Slicing_To_End. Now the compiler wants to find an == operator that fits. The only one is our custom operator that takes as input an integer on the first position and in the second Slicing_To_End and returns our type To_Range. You also could return of course other types like int,float.

Nostomania answered 4/10, 2009 at 2:4 Comment(0)
S
-4

Your suggestion would be nothing more than syntactic sugar for:

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

and in fact there are already a functions to do that in both cstring and std::string. Which is perhaps a bit like answering "is it a good idea?" but not quite; rather asking "why would you need/want to?"

Shroyer answered 4/10, 2009 at 2:4 Comment(4)
Well, that was just an arbitrary example I made up when I was told to split my post into a question/answer. ;) That being said, syntactic sugar is exactly the point. I love C++ because of the myriad ways you can express a solution to a problem (procedural, functional, oo, etc.). These tools give you the ability to go a step further towards representing a concept as naturally as possible. And of course there are less sober uses as well (as evidenced in IdOp examples). :POrthogenesis
And actually, the avg example (which I copied from the CustomOperators page) is probably a place I wouldn't use something like this. When you think about averages you think "the average of...". This makes avg(x, y) more appropriate than "x avg y". The "contains" language (which I also found on the CustomOperators page) does a better job illustrating this particular construct.Orthogenesis
I think comments like "why would you ever want to do that" are totally contraproductive. Apparently the OP WANTS to do it. It is not anybody's business to question WHY he wants to do it. it is also very frustrating for somebody who finds a post like this by searching for keywords, then getting his hopes up to find an answer for his own problem, and then just get this in your face answer "why would you want to do that". Luckily there are some constructive answers here.Venturesome
Had I said "why would you ever want to do that?" I might agree; but I was not quite that dismissive. Rather it was intended as advice to think about the benefits vs effort. As I recall I was addressing the OP's comment about the question not being explicitly a question of "is it a good idea?" It may be a good idea, but it is a lot of work perhaps for little benefit. Moreover the answer was posted 6 years ago; I might well post this more appropriately as a comment today. The OP commented and clarified his intentions at the time. I had noting to add to existing answers at the time.Shroyer

© 2022 - 2024 — McMap. All rights reserved.