User-defined infix operators
Asked Answered
R

1

8

It is easy to introduce new infix operators in C++

// User-defined infix operator framework

template <typename LeftOperand, typename Operation>
struct LeftHelper
{
    const LeftOperand& leftOperand;
    const Operation& operation;
    LeftHelper(const LeftOperand& leftOperand, 
               const Operation& operation)
        : leftOperand(leftOperand), operation(operation) {}
};

template <typename LeftOperand, typename Operation >
auto operator < (const LeftOperand& leftOperand, 
                 Operation& operation)
{
    return LeftHelper<LeftOperand, Operation>(leftOperand, operation);
}

template <typename LeftOperand, typename Operation, typename RightOperand>
auto operator > (LeftHelper<LeftOperand, Operation> leftHelper, 
                 const RightOperand& rightOperand)
{
    return leftHelper.operation(leftHelper.leftOperand, rightOperand);
}

// Defining a new operator

#include <cmath>
static auto pwr = [](const auto& operand1, const auto& operand2) { return std::pow(operand1, operand2); };

// using it
#include <iostream>
int main() 
{
   std::cout << (2 <pwr> 16) << std::endl;
   return 0;
}

Live demo

Unfortunately, this power operator has wrong precedence and associativity. So my question is: how to fix this? I want my <pow> to have higher precedence than * and associate to the right, just like in the mathematical notation.

Edit It is possible to vary the precedence by using different brackets, e.g. |op|, /op/, *op* or even, if one is so inclined, <<--op-->>, but one cannot go higher than the highest built-in operator precedence this way. But today C++ is so powerful with template metaprogramming and type deduction, there simply ought to be some other way to achieve the desired result.

Additionally, it would be nice if I could use pow and not pwr. Unfortunately in some implementations #include <cmath> brings pow into the global namespace, so there will be a conflict. Can we overload operator not such that a declaration of the form

not using std::pow;

removed std::pow from the global namespace?

Further reading: a related proposal by Bjarne Stroustrup.

Ragland answered 1/4, 2016 at 12:42 Comment(8)
"easy".......... ha ha. (like, seriously, I hope this is just for research purposes)Chip
You can't "invent" new operators, you simply use existing operators to emulate something working similarly to an operator but isn't an actual operator. That's why the operator precedence is not working. And to answer your question, there's no way to fix this.Pendergast
This is a bit similar idea to the "runs to" operator: while(i ----> 0). Very handy!Failing
@user2079303 The goes towards operator has only 2 -. I wouldn't be surprised if someone made something awesome with this operator definition thing in some math library.Validity
Haha, that proposal by Bjarne Stroustrup was a total troll :DFurness
Operator overloading doesn't change (and you cannot change) the precedence of operators. They are implemented in the core of the language, so there is nothing you can do about it unless you invent C+++ :)Freedman
Possible duplicate of Spaghetti-Harvest in TicinoBetti
Please please please please don't ever do something like this in production code!Seismograph
M
6

The principle of least surprise is important, and it is key that a*b *power* c * d evaluate to a* (b^c) *d. Luckily there is an easy solution.

To ensure that *power* has a higher precedence than multiplication, you have to use a similar named operator technique for multiplication.

Then instead of directly calculating the results of *power* and *times*, you instead build an expression tree. This expression tree, when evaluated, can apply arbitrary precedence rules.

We can do this with every built-in operator, giving us an easy to read syntax that permits compile-time metaprogramming of operator precedence:

auto z =equals= bracket<
  a *plus* b *times* c *power* bracket<
    a *plus* b
  >bracket *power* x *times* y
>bracket;

To avoid this expression template from being stored longer than optimal, simply overload operator auto()&& to return the deduced type. If your compiler fails to support that feature, =equals= can return the proper type at a mild cost of clarity.

Note that the above syntax is actually realizable in C++ using techniques similar to the OP's. An actual implementation is larger than a SO post should contain.

There are other benefits. As everyone knows, obscure ASCII characters in programming languages have fallen out of favor, and people reading C++ may be confuesed by expressions like:

int z = (a + b* pow(c,pow(x,a+b))*y);

With this technique, all operators have readable names that make their meaning clear, and everything is done infix instead of mixing infix and prefix notation.

Similar solutions to ensure that pow is available can be done by reimplementing <cmath> as <cmath_nopow> yourself. This avoids overloading operator not on language constructs, which causes AST grammar monads to decouple, and/or violates the standard. Maybe try Haskell?

Misleading answered 1/4, 2016 at 14:58 Comment(4)
This is a great Idea. If one always uses bracket/ ... /bracket for bracketing at the top level, it should be even possible to retain the standard * notation for multiplication! As for Haskell... I dunno, perhaps let Snoyman fix the Either monad first?Ragland
@n.m. I don't know, bracket/ looks confusing. We could use bra< and >ket, which fits prior use in quantum mechanics.Misleading
If using legacy one-symbol operators like * you need brackets with the highest possible precedence, so bra< >ket, attractive as they are, won't work. A bra/ /ket pair is probably the next best thing.Ragland
I think legacy raw * would just confuse programmers; imagine if someone had a variable called cross! It just is not worth bending over backwards to support users who won't keep current.Misleading

© 2022 - 2024 — McMap. All rights reserved.