What is unified function call syntax anyway?

One of the language proposals in the last few years that comes up fairly regularly is Unified Function Call Syntax, sometimes also called Unified Call Syntax. I’m going to use the former name, abbreviated as UFCS, because FLAs are the new TLAs.

What makes this particular proposal confusing in these kinds of discussions is that it’s not really one proposal - it’s multiple different proposals under the same umbrella. And I’ve found that people often discuss UFCS as if it’s one thing, but are actually talking about different flavors of it, despite all speaking with total certainty about what the UFCS proposal was. In the interest of alleviating that confusion, I wanted to write a post expounding on all the different variations of proposals in that space. My goal here isn’t to argue for or against UFCS, simply to explain what it is (or more accurately, what they are).

There are two axes of orthogonal decisions to be made in this space:

How are we expanding the candidate set?

How are we going to change the overload resolution rules to pick between the old candidates and the new candidates?

Overload Resolution Rules

In the mundane cases, either only the existing candidates exist or only the new candidates we add exist, so we don’t have to think about which one we choose. But this being C++, we always have to deal with the exception cases. So the question is - what do we do if there’s both a new and existing candidate? How do we pick? We, again, have multiple options:

OR1) We could prefer the candidate with syntax as written. That is, do overload resolution the old way and only if no candidate is found, then look up the new candidates. Non-member syntax would look up only free functions first. Member syntax would look up only member functions first.

OR2) We could consider all the candidates together and perform one overload resolution round on the whole set.

OR2+) Same as above… but prefer member functions in case of ambiguity.

OR3) We could always prefer the member function syntax, regardless of how the code is writen. That is, OR1, but treat non-member calls as member calls first.

Here’s a short code fragment demonstrating the differences between the approaches:

These overload resolution options aren’t completely orthogonal to the candidate set rules, since OR3 doesn’t apply with CS2 - since that one only makes sense if the non-member call syntax can find member functions.

P0301R1 (Maurer, Mar 2016): This paper actually introduces a new function call introducer such that .f(x, y) is the merged overload set (OR2+) of f(x, y) and x.f(y), with .x.f(y) being equivalent to .f(x, y).

As you can see, there are lots of different proposals here. One of them (P0301R0) went up for a vote in the Jacksonville meeting in March 2016 (non-member call syntax finds member functions only). You can see the results of that vote in the minutes. The initial vote was:

For

Abstain

Against

24

21

24

After some discussion, a five-way poll was taken:

SF

F

N

A

SA

13

11

18

21

5

This failed to reach consensus and was rejected. As far as I’m aware, nobody has done work on it since.

Where do we go from here?

Between all of the above papers, there are multiple problems trying to be solved, with multiple approaches to solving them, and multiple issues being brought up. If UFCS is important to you, that history needs to be addressed.

But most importantly, I would like to make sure that when we talk about UFCS, we make sure that first we agree on what we’re actually talking about.