
C++ unified call syntax: x.f(y) vs. f(x,y) [pdf] - tpush
http://isocpp.org/files/papers/N4174.pdf
======
lokedhs
Oh great. More magic syntax that makes one thing look and behave like another,
while not actually being that other.

Every time C++ gets new features, I feel like the designers must never have
looked at a C++ core dump, trying to find out what actually happened. The
source code bears practically no relation to what actually happens.

The C++ designers should perhaps stop trying to make their language into
Common Lisp. You can only fake dynamic behaviour so far until it breaks.

~~~
kensai
I really don't understand this kind of bash every time C++ gets some cool new
feature. A developer that does NOT want to use a feature can always not use
it. But why should we deny it to those who are interested and use it at their
own risk?

~~~
golemotron
The problem is that there is no way to reduce complexity in a programming
language that is serious about backward compatibility. C++ now has close to 30
years of non-trivial additions built on the craggy basis of backward
compatibility with C and its own feature set.

In the early 1990s, it was complex. The Annotated Reference Manual that was
used as the de facto standard was full of edge cases and detail that required
a great deal of study. No other language since has approached that level of
complexity and I'm talking about what it was like before templates.

I sincerely believe that C++'s downfall is underway and it comes from a very
simple fact - it's increasingly hard (read impossible) for new C++ programmers
to go from zero knowledge of the language to being able to read and maintain
modern C++ programs that have taken full advantage of the feature set over the
past decades.

~~~
pjmlp
Yes it is complex, but you can say the same about almost any modern language.

If the language is not complex, then it is the eco-system.

~~~
golemotron
Yes, but on balance, I think that ecosystem complexity is both more tractable
and addressable than language complexity.

~~~
pjmlp
I think it is relative.

An ecosystem can also be quite complex, specially if it changes often.

Any newcomer will face the exact situation not knowing what to use, what is no
longer best practice or what should be avoided.

------
vidarh
The interesting part to me with this, is that for the last 20 years or so
there's been a certain strain of C++ development, spurred on by increasing
template/meta-programming support, that have advocated for moving more code to
non-member functions, to benefit from improved reuse.

But this has been awkward because it means calling conventions may often be
different and functionality that's "mixed in" this way seem far less
integrated than functionality that's brought in via inheritance. It's also had
the disadvantage that if you ever suddenly needed one of those free-standing
functions to depend on internal state of an object, you'd either have to break
encapsulation, or break the API (by moving the function into the class), or
provide a legacy function to forward to the new API.

This change would make it even more attractive to break out functionality that
does not depend directly on the internal state of an object as standalone
functions, in effect creating a sort-of "limited class re-opening".

~~~
phkahler
>> This change would make it even more attractive to break out functionality
that does not depend directly on the internal state of an object as standalone
functions, in effect creating a sort-of "limited class re-opening".

You can already write the functions that way, nobody is stopping you. f(x,y) a
function that returns something based on x and y. x.f(y) a method where object
type x does something based on y.

The second form will have access to private members of x and has some notion
of the object doing something. The first form implies that x is just an
argument to the function.

What I see is C++ going from object oriented to functional - in other words
following whats trendy.

That said, I think they should take all the lessons learned doing high
performance implementations of C++ and start over to create a new language
that doesn't suck with all the baggage.

~~~
yohanatan
> That said, I think they should take all the lessons learned doing high
> performance implementations of C++ and start over to create a new language
> that doesn't suck with all the baggage.

Oh, you mean Rust (programming language)?

------
toolslive
I would expect some comments on covariance here. With this you seem to have a
magic first parameter x that gets to be dispatched based on the runtime type
of x, while y isn't and gets to be overloaded according to its static type.
The dot notation at least conveys some notion of x being looked at in a
different way than y.

------
verytrivial
This idea _almost_ sounds like a joke. A member function is indeed different
from a non-member function precisely because it is not a member of the set of
functions responsible for maintaining the object's invariants. Treating

    
    
        f(x, y);
    

as

    
    
        x.f(y);
    

only makes sense (to me?) if all other things being equal, if f(x, y) is a
friend of the class. And picking the member function form over the non-member
function in the caller's scopes seems like a step _away_ from encapsulation.
Prefer non-member functions. Scott says so!

~~~
vidarh
Consider that under the hood, they result in the same code (unless f is a
virtual member function, in case it'd be equal to x.<vtable ptr>[vtable offset
of f](x,y)).

For non-virtual member functions they differ in syntax and lookup, not
implementation. In effect they provide two different syntaxes for something
that is _very_ often the same thing, namely executing a piece of code that
logically "belongs" to and operates on the first argument.

E.g. look at the C standard library, and consider how many of the functions
there operates on the state of the first argument.

This change would let you write an f() that does not have access to the
internals of x, yet can still be called the same way as the member functions,
providing increased uniformity and making the choice of member function vs.
non-member function more of an implementation choice by letting you reduce the
impact on the documented API:

> And picking the member function form over the non-member function in the
> caller's scopes seems like a step away from encapsulation. Prefer non-member
> functions. Scott says so!

Preferring non-member functions is exactly why I think this seems interesting.
The awkwardness of splitting an interface so people have to remember what is a
member function and what is a non-member function, and having to decide how it
should be split, easily results in a lot of stuff ending up as member
functions when it doesn't have to be.

If it's transparent to the user, there are fewer reasons to prefer member
functions, not least because "promoting" a non-member function to a member
function if you later need access to more internal state becomes easier.

------
pmontra
First of all, don't flame me too much because if you follow my advice it
wouldn't be C++ anymore. This is not a suggestion about the post but an invite
to think.

I always wondered why somebody designs object oriented languages with
functions and eventually run into this kind of problems.

An answer might be that they're not willing to wrap fundamental types with
classes, even if it would be only syntactic sugar that the compiler can easily
unwrap without losing efficiency in the generated code. Probably this is not
the only reason, as for the case of Python's len() and the like. Python's
designer actually wanted functions to make sure all classes had the same
methods, exported as functions.

Furthermore there might be historical reasons, in the case of C++ to make it
look like C. Too many differences can scary people away. Think about what it
took to make Objective-C successful, the almost impossibility to use any other
language to program on a given platform.

Finally, there is convenience, as in void fn(int x) {...} main() { ...; fn(x);
...} By the way, Ruby solves that by adding fn to the Kernel class so it
allows the same convenient syntax in a complete OO world.

Anyway, an OO language in which everything is an object doesn't suffer from
the kind of problems the post is trying to solve. Years ago I went to Ruby
from Java and a little of C++. I was happy not to have that kind of int vs
Integer duality. Then I looked at Python and found it a little bit weird with
functions mixed to methods, but that's the way it is.

So my advice is as radical as unrealistic in the case of C++: if you want to
change the language, make it use methods where it still uses functions. The
compiler will deal with that. Remember that when you'll design your next OO
language :-)

~~~
bcoates
In early-bound, single-dynamic-dispatch languages it makes a small amount of
sense, as x.fn() is the only syntax for a dynamic dispatch against object x
and fn(x) uses at most compile-time information to determine what fn gets
called.

------
adrusi
I like the idea of unified call syntax. I know D and Nimrod support a system
similar to the one proposed here, albeit they were designed with the syntax
from the start and avoid the more awkward edge cases.

If it can be proven not to break existing code, I think it would be a positive
addition to the language. I worry, however, that there might be some
confusion. I imagine seeing x.a().b().c() where you expect c(b(a(x))) would
throw you off.

An alternative worth considering is what Kotlin calls extension methods (and
I'm sure I've seen the idea elsewhere). The idea is you could declare a member
or non-member function to always be called with either call syntax. This
eliminates the burden on the programmer of choosing which syntax to use,
eliminates a lot of edge cases, but doesn't give the same flexibilty as the
unified syntax.

------
byuu
Oh, wow. I know most people dislike C++ language extensions, given how complex
the language already is. But as a library author, I truly believe this is the
most powerful and useful addition we can add to the language now. I've been
wanting this exact thing for years, but unfortunately you can't even do it as
a preprocessor due to the complexity of ADL and SFINAE on lookups.

First, I like Herb Sutter's propsal more (
[http://isocpp.org/files/papers/N4165.pdf](http://isocpp.org/files/papers/N4165.pdf)
), as Bjarne's proposal to favor x.f() when encountering f(x) would be a
breaking change. However, Herb's suggestion to accept x in any position in f
(eg x.f(a, b) could match f(a, x, b)), in order to wrap libc interfaces such
as FILE* more easily, I feel is a mistake. I'd rather we keep the complexity
of parsing down and write wrapper headers around libc to support the new
syntax instead (which is also a wonderful opportunity to get the functions and
#defines out of the global namespace.)

Onto utility ... as stated, nearly all of my time writing C++ is spent
creating libraries. This is always a huge struggle: you don't want users to
have to remember that some functions are class::func() and others are
func(class&), and indeed it makes for some really ugly code, eg c(b(a())
instead of a().b().c(). Further, since C++ classes cannot be reopened, it's a
real problem to just rely on users to extend your classes to add desired
features. Person A makes class fooObject : object, person B makes class
barObject : object. But now fooObject and barObject are incompatible, unless
you slice back to object, and lose the whole point of your extensions.

Unified function call syntax (UFCS) solves this nicely: member functions are
now only those that need access to private state. Your classes are much
smaller, which aids in encapsulation. Further, you don't have to code
everything but the kitchen sink into your classes anymore, the user can simply
add functionality that they need. Or you can offer the extensions piecemeal in
separate headers. So not only do we get UFCS, we also get a nicer form of C#
extension methods for free.

Further, it's a real boon to IDE auto-completion features to be able to know
available functions after object.[function] than it is after
function([object]; the former is much easier. Imagine an IDE trying to auto-
complete "begin(", for instance. That list is going to be hopelessly long.

Lastly, it will be very interesting to see what rules they put in place around
this and primitive types. If you can have square(int&), then you could call
int x = 5; int y = x.square(); But even more interesting will be if this is
allowed for constants: int x = 5.square(); string y = "hello".toUppercase();
... there is the potential to allow C++ to be a truly object oriented language
where everything is an object, if this is done right.

On that note, I haven't seen much talk around the implementation of f(x), but
they definitely need to allow for both x::f() -> f(x&) and x::f() const ->
f(const x&). I'm also a little concerned about x->f() -> f(x*), and how that
might play in with overloaded operator-> that you find in smart pointer
classes.

For those in favor of f(x, y) over x.f(y), please consider the history of
ambiguity around operator arguments. strcat is (target, source) whereas rename
is (source, target). There are thousands of examples like this. Putting the
target before the function call is an absolutely wonderful way to remove the
burden of having to remember each and every function's exact ordering. For
cases where there is no clear "target", regular function call syntax can still
be used, eg intersect(x, y)

But, complexities aside ... I really hope this feature makes it in, and that
the naysayers to all language extensions do not ruin this. It will completely
change the way I program in C++.

~~~
angersock
What is a use case in your library code where you have the problem of
class::func() vs func(class&)? I'm willing to wager your API is just plain
written badly in that case.

 _It will completely change the way I program in C++._

This is a bug, not a feature.

I kind of think, at this point, Bjarne is trying to get the language to
implode once and for all so he can finally get some rest--and only has to take
such drastic measures because the community is too enamored with shiny to just
let the old dog die.

~~~
byuu
I must not have explained it well enough.

Take std::string. It is missing majorly useful functions that exist in other
languages, such as trim, transform, split/explode/tokenize, replace-by-string
(std::string is replace-by-length, which is more like memcpy), etc.

You can add those functions. But you can't add them into std::string. So you
get: void trim(string& s, const string& trim);

And now your code is a mix of two styles:

    
    
        s.append("  Hello, World  ");
        trim(s, " ");
        s.compare(...);
    

As I've explained, subclassing is dangerous. Two people each add their own
functions to their own derived types, and now those types aren't directly
compatible without slicing back to std::string.

So the problem, as a class author, is that you want to add all of the
functions a user might want, so that they can consistently use the same
syntax. So when you write your string class, users can then use it like so:

    
    
        s.append("  Hello, World  ");
        s.trim(" ");
        s.compare(...);
        //or even possibly ...
        s.append("  Hello, World  ").trim(" ").compare(...);
    

(You may say the syntax difference doesn't matter, but I disagree strongly:
it's burdensome to remember which functions are string::foo(), and which
functions are foo(string&); and if I wanted to use foo(string&) exclusively,
then I'd go back to writing in C.)

This causes substantial bloat by packing string with a ridiculous number of
functions.

Some of those functions are going to be very niche. For instance, I have
find/replace/split functions that ignore values inside of quotes, which I use
extensively in a cross assembler I wrote. But they're not very useful for my
other programs. It would be nice if I could #include <string/extension/quoted-
strings.hpp> for the cross assembler, which would contain UFCS functions, and
leave this out of my other projects. Smaller compilation times, and easier to
create experimental/fluid extensions before committing them to the official
class API.

And if a user of my string library decided that they needed additional
functions, they too could add them.

I would go so far as to say that UFCS will result in more developers using the
C++ standard library containers and types, rather than rolling their own.
Which would be a huge win for the language.

------
nly
Even though it makes sense with regard to the way operators work currently
(they can be defined as members, or as free functions), it looks like a
breaking change to me

    
    
        struct A {
            double magic(int);
        };
    
        string magic(A&, int);
    

is currently legal code. Under this proposal they'd effectively be overloads
with different return types, which is is a compile-time error, unless I'm
missing something.

~~~
adrusi
In order to maintain compatability, magic(a,b) would have to call the
nonmember function and a.magic(b) would call the member function. Ideally, if
you were using this feature, you would enable a warning that tells you if
there's a collision like this in any scope. Otherwise, your code would
continue to work just as it already does.

Unless I'm missing something, I don't think this proposal would break
anything, and while the edge behavior for collisions is awkward and
unfortunate, at least its easy to identify and avoid

------
oleganza
Apple's Swift makes more sense with its curry feature. If you have class 'C'
with method 'm' taking argument 'x', you can call that method either like
c.m(x) or C.m(c, x) because C.m is automatically provided closure taking
instance of C (typically as "self") and argument x.

Most importantly, there is no magic going on with namespaces. Method 'm' still
belongs to its class's namespace.

------
lelf
Note: it's just a proposal. (And I'd say highly unlikely to be ratified: could
break things, don't bring anything.)

~~~
bodyfour
I don't know what it's chances of ratification are -- it does seem like an
aggressive change -- but I disagree that it doesn't add anything. This would
actually fix one of the biggest problems I have with large C++ development:
non-open classes.

Think of a widely used class -- for example lets use std::string. Maybe for
your program there are some missing methods that would really handy to add,
like maybe .urlencode(). Today in C++ you can't though: if the method isn't
mentioned in the original definition of std::string in its header file it's
permanently missing.

This leads to pressure to make a custom version of the class, either
inheriting or encapsulating the original. This seems to work OK but then can
lead to problems if you later want to interface with someone else's library
written in C++. For instance you might have "class mystring : public
std::string" and they have "class theirstring : ..." now you have to
explicitly convert between them even if data-wise they're 100% compatible.

This proposal neatly solves this problem -- you could just implement string
extensions as a function and still be able to call them like a regular method.
The ability to add methods to other people's types promotes type reuse.

~~~
jpatte
The same result could be achieved by introducing a smaller change to the
language that doesn't completely redefine the way existing functions are
evaluated. By using some specific signature (like adding a keyword to the
first argument of a function), you could indicate that this function may be
used as a member method of the first argument. For example, C# uses the
keyword 'this' for its extension methods:

    
    
      public static class Helpers
      {
        public static string UrlEncode(this string url)
        {
          //...
        }
      }
    
      // then later:
      string someText = "...";
      string a = Helpers.UrlEncode(someText); // valid
      string b = someText.UrlEncode(); // valid too
    

But of course that would mean having to update all the function declarations
of the standard library to add the new keyword when needed...

~~~
bodyfour
Yes, I agree that there are simpler changes that would fix the problem, and
personally I'd root for them. Stroustrup's proposal is still interesting in
that it would solve it and unify operator/method dispatch at the same time. It
has a certain elegance.

------
oleganza
Stroustroup tries to bend your API to his liking only because in his opinion
certain syntax is "older" and more "general". Alarming quotes:

"The functional (mathematical) notation is far older and more general than the
object-oriented dot notation."

"For example, I prefer intersect(s1,s2) over s1.intersect(s2)."

~~~
zura
I'd also prefer "intersect(s1, s2)" if C++ had multimethods...

But to follow the line of Bjarne's advice (I've also heard same from
Stepanov): One should prefer non-member functions. i.e. there must be some
real reason for some function to become a member of a class.

~~~
throwawayaway
The problem with non member functions is that, once you start preferring them,
you are basically writing C. They are contagious. There's very little reason
for class members unless you want inheritance or polymorphism. Scott Meyers
has also promoted them:

[http://www.drdobbs.com/cpp/how-non-member-functions-
improve-...](http://www.drdobbs.com/cpp/how-non-member-functions-improve-
encapsu/184401197)

With non member functions declared in the header and defined in a .C file, and
suddenly you don't have RIAA any more or exceptions. Your .C files end up
being your division of concerns, not classes.

The only time you end up using any C++ features is interacting with a library
that prefers member functions.

~~~
zura
Well, the key thing is the `reason` here. In C++ we use RAII, so this is the
reason to have a member constructor/destructor functions instead of non-member
create_something(...)/destroy_something(...) (aka C style).

But, for instance, `sort` is a good candidate to have outside of a class - and
it actually is a global function - std::sort.

~~~
throwawayaway
RAII is your reason.

When you start preferring non member functions, you tend allocate on the
stack.

There are rare items that require heap allocation and other resources. For
those you can have classes. What I am saying is that when you prefer non
member functions you end up with very few classes.

~~~
nikbackm
You don't use classes only to get RAII. They are also useful when you have
data with invariants, which the constructor establishes. And of course, if you
want to use private data.

~~~
throwawayaway
by invariants do you mean constant values?

there's many ways to implement private data. if you prefer non member
functions, a class is a bunch of extra keystrokes to achieve the same as the
old fashioned C private data.

if you prefer non member functions to member functions, your private data
becomes a global variable or struct in a file, and the private data is not
accessible by getter/setter functions that are externalised.

~~~
zura
Having access to private data (implementation details) is another reason to
have a member function.

Unless you're a fan of `friend` keyword ;)

~~~
throwawayaway
i think you need to read my comment again because "private:" in a class is not
the only way to implement private data. you are only repeating what the parent
commenter said.

~~~
zura
Well, to follow your line - virtual functions can also be implemented with
some function pointer stuff within C. We're not discussing implementing
various C++ stuff in C, we're talking about C++ itself.

~~~
throwawayaway
be that as it may, it requires more keystrokes to implement virtual functions
in C than it does in C++.

however, it requires less keystrokes to implement private data in C, than it
does to create a class to store private data.

therefore it's questionable, if you are preferring non member non friend
functions, whether you should use classes when you want private data.

i refute the idea that private data is a reason to use classes, if you are
preferring non member non friend functions as advocated by Stroustrup, Meyers
and Stepanov.

as i said already

> There's very little reason for class members unless you want inheritance or
> polymorphism.

------
CountHackulus
Considering that this is already the case in D, it seems like it might be a
good idea. Though who knows how much havoc this will cause in older code bases
that are riddled with hacks.

------
KyleSanderson
The traditional C++ syntax to me is the most clear, hiding functions under
passing the hidden _this_ pointer just brings the entire thing seemingly back
to C.

------
ska
... and then it's a short step to adding multiple dispatch in a clean way.

I'm pretty sure that's not what Bjarne has in mind, though.

~~~
vinkelhake
Why are you sure about that? It's explicitly mentioned in the paper and Bjarne
has done research in the past on adding multiple dispatch to C++.

[http://www.stroustrup.com/multimethods.pdf](http://www.stroustrup.com/multimethods.pdf)

~~~
ska
Interesting, thanks. Color me much less sure then.

------
ANTSANTS
And so C++ takes another step towards Lisp.

------
Heliosmaster
Not sure if I like this. Smells like the Greenspun's tenth rule all over the
place.

------
tomp
I'm not a C programmer, but in general, I think this is a great idea. I really
wish that it could be added to a high-level language. However, I think it's
fundamentally incompatible with some of the features of high-level languages,
such as dynamic typing, first-class functions, partial application, parametric
polymorphism, structural typing/extensible records...

~~~
vidarh
I'm uncertain what languages other than C++ you think would benefit much from
this.

The reason C++ would benefit from this in the first place is because of a
combination of its lack of higher level features (e.g. enabling
extension/mixing in functionality in less painful ways) and its "C legacy" in
the form of vast amounts of non-OO code.

E.g. consider a language like Ruby, which has most of what you list as
features of "high-level languages". For Ruby this would be very weird to begin
with, because Ruby does not have non-member functions - everything calleable
in Ruby is a method; what looks like free-standing functions in Ruby are
functions on an object whose eigenclass is the implicit outermost scope.

What we'd do instead in Ruby is to put "function like" methods in a module
that we can include in whatever objects we want.

But you _can_ in fact easily achieve what this proposal does in Ruby.

~~~
tomp
Many languages that are not primarily object-oriented. E.g. it was proposed in
Rust [1]. It could also be added to many other functional languages, as often
the "object-oriented" function call syntax looks more natural (when the first
parameter is "special", the object of the call) than the "mathematical"
function call syntax (appropriate for when parameters are equivalent, e.g.
`max()` or `dot_product()`).

[1] [https://mail.mozilla.org/pipermail/rust-
dev/2013-October/006...](https://mail.mozilla.org/pipermail/rust-
dev/2013-October/006034.html)

