DEV Community

Swastik Baranwal
Swastik Baranwal

Posted on

C++ Named Operators

As I was revising C++ again because I am not using it daily but today what I have discovered is something that many people and even Fluent C++ Programmers don't know about. I am about to introduce Named Operators.

This post uses klmr/named-operator library because it is convenient to explain and to look at.

Named Operators

Named Operators are those operators which are custom named and are surrounded by symbols like +, -, * etc. It is similar to how Haskell does it.

std::vector<int> = {1, 2, 24};
bool result = 24 <in> vec;
Enter fullscreen mode Exit fullscreen mode

Definition

Operators can be defined for any function-like object by calling
make_named_operator:

auto div = make_named_operator(div);
Enter fullscreen mode Exit fullscreen mode

where

int div(int x, int y) {
    return { x / y};
}
Enter fullscreen mode Exit fullscreen mode

Or, if you prefer functors (and yes, templates work just fine):

auto append = make_named_operator(append_t());
Enter fullscreen mode Exit fullscreen mode

with

struct append_t {
    template <typename T>
    vector<T> operator ()(vector<T> const& vs, T const& v) const {
        auto copy(vs);
        copy.push_back(v);
        return copy;
    }
};
Enter fullscreen mode Exit fullscreen mode

And of course lambdas work as well:

auto in = make_named_operator(
    [](int i, vector<int> const& x) {
        return find(begin(x), end(x), i) != end(x);
    });

bool result = 24 <in> vec;
Enter fullscreen mode Exit fullscreen mode

Design rationale & implementation

Overloading operators with unconventional semantics generally frowned upon because it violates the user’s expectations (although it has variously been used to great effect).

Furthermore, the set of operators that can be created in this fashion is limited to a subset of the built-in operators.

On the other hand, using infix notation instead of function calls can undeniably make code more readable, especially when nesting lots of operations. Compare

auto result = contains(set_minus(set_minus(A, B), C), x);
Enter fullscreen mode Exit fullscreen mode

and

auto result = x <in> (A <set_minus> B <set_minus> C);
Enter fullscreen mode Exit fullscreen mode

Other languages have recognized and addressed this problem.

Since C++ allows overloading operators for custom types, named operators can be implemented by simply sticking a place-holder object between two overloaded operators (which can be entirely arbitrary):

struct some_tag {} op;
struct op_temporary {};
op_temporary operator <(int lhs, some_tag rhs);
int operator >(op_temporary lhs, int rhs);
Enter fullscreen mode Exit fullscreen mode

These declarations are enough to make the following syntax valid:

int x, y, z;
z = x <op> y;
Enter fullscreen mode Exit fullscreen mode

Of course, what the compiler really sees is

z = operator>(operator<(x, op), y);
Enter fullscreen mode Exit fullscreen mode

This already highlights a problem: operator precedence. In effect, op will have the precedence of its surrounding operators. In particular, the precedence of < and > is very low

GitHub Repo

GitHub logo klmr / named-operator

Named operators for C++

Named operators

tl;dr

The following code is legal C++ and does exactly what you’d expect it to do.

auto result = "Hello" <repeat> 3 <join> ", "
std::cout << result << '\n';
Enter fullscreen mode Exit fullscreen mode

Output:

Hello, Hello, Hello

This project explains how.

Background

Named operators are (user-defined) operators which have names rather than symbols. Here’s Haskell:

x = a `div` b
Enter fullscreen mode Exit fullscreen mode

and here’s R:

yup <- 4 %in% c(1, 2, 3, 4, 5)
Enter fullscreen mode Exit fullscreen mode

In fact, C++ also has named operators – alternative tokens for the primary ones defined, in §2.6.

But those are fixed and not redefinable. Sure, you can #define your own names for tokens …

#define PLUS +

But this has all the usual disadvantages of macros and limits you to the already existing binary operators. Until now.

Take a look at this fully valid, macro-free…

Note

This work and implementation is done by klmr so I don't take any credit of this work! I am just posting and sharing this knowledge!

Discussion (0)