While looking into uniform call syntax in C++, I stumbled onto how some libraries use the | operator to create pipable functions. Paul Fultz II has a good tutorial on how to create your own pipable functions. However, I wanted to also make existing free functions pipable without too much boilerplate and by chance I found that it can be done with very little code and a lot of unknown consequences:

#include <iostream>
#include <cmath>

using namespace std;

struct Vector3 {
    double x;
    double y;
    double z;
};

double length(Vector3 a)
{
    return sqrt(a.x*a.x + a.y*a.y + a.z*a.z);
}

template<class T, class F>
auto operator|(T&& x, F p)
{
    return p(std::forward<T>(x));
}

int main()
{
    Vector3 a{12, 4, 2};
    double len = a | length;
    cout << len << endl;
    return 0;
}

This has a lot of limitations that I will discuss in the following. But first, let me put this into context.

Background

In 2015, Herb Sutter and Bjarne Stroustrup formalized a proposal to include uniform call syntax in C++. This would allow calling free functions as if they were member functions:

#include <iostream>
#include <cmath>

using namespace std;

struct Vector3 {
    double x;
    double y;
    double z;
};

double length(Vector3 a)
{
    return sqrt(a.x*a.x + a.y*a.y + a.z*a.z);
}

int main()
{
    Vector3 a{12, 4, 2};
    double len = a.length();
    cout << len << endl;
    return 0;
}

This is a great idea! Seeing traits in Rust made me realize I want something like this in C++:

struct Vector3 {
    x: f64,
    y: f64,
    z: f64
}

trait Length {
    fn length(&self) -> f64;
}

impl Length for Vector3 {
    fn length(&self) -> f64 {
        return (self.x*self.x + self.y+self.y + self.z+self.z).sqrt();
    }
}

fn main() {
    let a = Vector3{x: 12.0, y: 4.0, z: 2.0};
    println!("Length is {}", a.length());
}

Notice how length is a feature added to the Vector3 class after the fact, rather than something that must be included by the original developer who implemented Vector3.

In C++, we can “extend” a class using free functions, but the syntax is not as nice:

#include <iostream>
#include <cmath>

using namespace std;

struct Vector3 {
    double x;
    double y;
    double z;
};

double length(Vector3 a)
{
    return sqrt(a.x*a.x + a.y*a.y + a.z*a.z);
}

int main()
{
    Vector3 a{12, 4, 2};
    double len = length(a);
    cout << len << endl;
    return 0;
}

While free functions, like traits, gives the ability to extend existing classes with more functionality, the syntax is cumbersome in cases where multiple operations are performed:

Vector3 point{4, 5, 6};
translate(rotate(scale(point, 10), 24), 1, 2, 3);

Compare this to what we can do in Rust:

Vector3 point{4, 5, 6};
point.scale(10).rotate(24).translate(1, 2, 3);

I would argue that the second syntax is much more readable. We can get this in C++ by adding scale, rotate and translate as member functions to Vector3, but what if we want a composition of translation and rotation in one function? I can obviously not add every thinkable combination as a member function. And if I use free functions, I would have to use the first syntax shown above.

D solves this with a uniform call syntax that makes the following two lines equivalent if there is a free function length(Vector3) available:

length(a);
a.length();

And this is what was proposed by Herb Sutter and Bjarne Stroustrup, but didn’t make it to any of the C++11/14/17 standards.

While I understand that many are worried about the consequences of adding the ability to call arbitrary free functions as if they were member functions, I think it would be great if we could do so.

Jens Maurer and others have proposed an alternative notation for the uniform call syntax, by adding a dot in front of the object, making the following two lines equivalent:

length(a);
.a.length(a);

This gets around any problems that may arise with existing code.

Jonathan Coe and Roger Orr on the other hand proposed extension methods for C++, which I believe would be more similar to traits in Rust.

These proposals are all nice and bring great ideas to the table, but what can we do to get this right now?

Pipable functions

Boost.Range, range-v3, Linq and more have implemented pipable functions to achieve similar functionality today. This allows you do things like:

int number_3 = 1 | add_one | add_one;
cout << number_3 << std;

As mentioned in the beginning of this post, Paul Fultz II has a good tutorial on how to create your own pipable functions.

When I played around with the above examples, I discovered that a simple operator overload actually makes existing functions pipable as well:

template<class T, class F>
auto operator|(T&& x, F p)
{
    return p(std::forward<T>(x));
}

This does have some big drawbacks. First of all, it clutters the entire namespace with an operator overload that the compiler will forever try to match with any object. This will result in error messages about p not being callable whenever you try to do a bitwise on something that doesn’t support it.

But hey, we wanted this for every function. The error messages will only disturb us if we do something wrong, so let’s move on and pretend everything is okay.

A bigger problem is that calling a function with an argument is not possible:

Vector3 a{12, 4, 2};
Vector3 b = a | scale(2);

This won’t work because scale is called before operator|. However, we can work around this by wrapping the function in a struct:

struct scaler
{
    Vector3 operator()(Vector3 a) {
        return scale(a, m_scale);
    }
    double m_scale;
};
// ...
Vector3 b = a | scaler{2};

That is fine, but it requires too much boilerplate if we have to do this for every free function we would like to call this way.

Another problem is that overloaded functions cannot be deduced. If we have two overloaded implementations of length, we get an error:

// ...
double length(Vector3 a)
{
    return sqrt(a.x*a.x + a.y*a.y + a.z*a.z);
}

double length(double a) {
    return a;
}

int main() {
    Vector3 a{12, 4, 2};
    double len = a | length;
}

This is the resulting error message:

error: no match for ‘operator|’ (operand types are ‘Vector3’ and ‘<unresolved overloaded function type>’)
     double len = a | length;
                    ^
note:   template argument deduction/substitution failed:
note:   couldn't deduce template parameter ‘F’
     double len = a | length;
                      ^

But we can work around this using a lambda:

Vector3 a{12, 4, 2};
double len = a | [](auto v){return length(v);};

That’s not exactly what we wanted, but it might be something we can work with.

Let us try a constexpr lambda:

// ...
const constexpr auto lengther = [](auto v){return length(v);};

int main()
{
    Vector3 a{12, 4, 2};
    double len = a | lengther;
}

The downside is that we need to do this for every thinkable free function we want to use. And as before, we have no way to pass arguments.

I really wanted to avoid macros, but here goes:

// ...
#define _(F, ...) [](auto&& v) -> decltype(auto) {return F(std::forward<decltype(v)>(v), ##__VA_ARGS__);}

int main()
{
    double len = a | _(length);
}

Yuck. Not only is it a macro, but I even named it an underscore.

But it works.

And we even got a fancy syntax for arguments:

Vector3 b = a | _(scale, 2);

Okay, let’s be honest. Uniform call syntax is nice, but not so nice that this is okay.

I think the way to go is to create structs with operators. But then again, you might as well just go for Paul’s solution. It involves a bit more code, but it won’t try to overload every operator| in your entire application.