
A bit of background for the unified C++ call proposal - ingve
https://isocpp.org/blog/2016/02/a-bit-of-background-for-the-unified-call-proposal
======
lholden
>> I received email accusing me of “selling out to the OO crowd” and people
whose experience and opinion I respect insisted that x.f(y) finding f(x,y)
would seriously compromise their ability to design stable interfaces.

I _think_ the concern is that a class "owns" its methods either directly or
through inheritance. Therefor, the interface is expressed by this namespace
and it can't be "invaded" or modified. From that perspective, having f(x,y)
being callable as x.f(y) would be a violation of your interface.

But... the dot syntax does not make an interface, and neither does it make
"object oriented programming". It implies that "f" is a specialized function
for somehow applying "y" to "x".

If you have a generic "f" that can be compiled to a specialized "f" for your
type... you are just adding composability to your language. That's a good
thing! Calling "f" as x.f(y) in this case makes a lot of sense. It's just
another specialized function for applying "y" to "x".

~~~
DrDimension
I'm going to have to pose an educated disagreement.

According to the classic C++ article, "What's in a class?" by Herb Sutter
([http://www.gotw.ca/publications/mill02.htm](http://www.gotw.ca/publications/mill02.htm))
-

The Interface Principle suggests -

For a class X, all functions, including free functions, that both (a)
"mention" X, and (b) are "supplied with" X are logically part of X, because
they form part of the interface of X.

So yes, f(x) is part of x's interface in C++, and has been considered to be so
for a long time.

~~~
lholden
"I'm going to have to pose an educated disagreement."

A little awkward to read in response to my comment.

An educated disagreement to what? I guess you are disagreeing with their
argument? I agree with your comment.

Or maybe it's a disagreement with what I think their argument is? This is why
I am asking. :)

------
DrDimension
Dear Bjarne,

Please, please, please reconsider not enabling x.f(y) resolve to f(x, y).

You must explain to the detractors that they must reconsider allowing x.f(y)
to resolve to f(x, y). This is not a selling out to OO, but in fact the
opposite! Allowing x.f(y) to resolve as such enables us to finally get _away_
from OOP by using an alternative style called 'Data Abstraction Style'. I have
written up an example of this style here -
[https://github.com/bryanedds/das](https://github.com/bryanedds/das)

I have also written an entire C++ core library in said style here -
[https://github.com/bryanedds/ax](https://github.com/bryanedds/ax)

In PLT terms, data abstraction is the dual of OOP. In fact, I use it
significantly in F# as a way to do pure functional programming where others
just fall back into OOP -
[https://vimeo.com/128464151](https://vimeo.com/128464151)

Data Abstraction Style with resolution of free-standing functions to dot
syntax gives us the best of both worlds - the increased modularity and
extensibility of free-standing functions as well as the nice tooling and API
explore-abily of the dot intellisense.

Additionally, there are precedence for this functional is less OOP-y language
like D and Rust - [http://www.drdobbs.com/cpp/uniform-function-call-
syntax/2327...](http://www.drdobbs.com/cpp/uniform-function-call-
syntax/232700394) [https://github.com/rust-
lang/rust/issues/16293](https://github.com/rust-lang/rust/issues/16293)

Finally, this syntax is important just to allow extension methods without a
more specialized syntax that won't likely appear anyways.

Please pass along this information to the people holding out on allowing
x.f(y) resolve to f(x, y) - it is not selling out to OOP - it's an elegant
path to finally move beyond it. People must be made to understand this before
making their final decision!

------
mappu
I have a pet language, in which `f.g(x)` is merely syntax sugar over
`{typename of f}_g(f, x)`. It's a combination of two ideas - the colon
operator for method calls in lua, and extension methods from C# &c.

I think this debate is a small part of a larger one about unified namespaces,
and for functions in particular. There's no good reason for a named function
to coexist with a like-named function variable. Javascript gets this a right
by having `function f` largely equivalent to `var f = function`.

I'm impressed that C++ continues to evolve 37 years later. It's a stark
contrast to, say, the Go 1 Compatibility Promise.

~~~
marvy
C++ tries to make the same guarantees that the Go 1 compatibility promise
makes. (Or at least very similar; they are different languages after all.)
Basically: old code should keep working possible, but they have the right to
invent new features. The real difference is that C++ is feature-philic (new
feature: want!), while Go is feature-phobic (new feature: avoid!)

Of course, if you want to make specific predictions about which features C++
or Go will adapt, then you need to look at details. For instance, Stroustrup
tried hard in the early days to ensure that user-defined types are on equal
footing with built-in types, while Go tried hard in the early days to make
sure that very common foundational types are built in and have good support.
Thus today the C++ standard library has two at least two distinct dictionary
types, while Go has one built in to the language. (Someone who actually knows
more about Go history than I do, please correct me if I'm wrong.) I tend to
favor the C++ approach as more mathematically pleasing, but I can't deny that
the Go approach leads to a simpler language. (The real question is: which one
leads to better programs, and I suspect the answer is "it depends, on way too
much stuff to even list".)

~~~
blub
There are several dictionary types in C++, but each has different performance
characteristics and they all have good support. Thanks to the efforts to make
built-in types and UDTs equal, Qt's map was for instance implemented as a skip
list and was just as usable as the standard library types. Nowadays it's a RB
Tree.

Go's strategy of picking hash maps and not offering the mechanisms of building
your own data structures with the same syntax affordances and performance
characteristics makes it easy to jump in and hard to make progress if you hit
a wall with the built-ins.

~~~
marvy
Yep. I guess they're counting on that being so rare that it's worth paying the
price in occasionally having to rewrite (and uglify) large chunks of code, in
exchange for having a simpler language.

------
blt
Ahh I'm sad. I really wanted x.f(y) to find f(x,y). Then std::algorithm and
ranges would combine to make sweet linq-like filter/map operations.

~~~
eco
Yeah, I'm really disappointed. It's one of my favorite things about D. I guess
f(x,y) finding x.f(y) is better than nothing but the other way around makes
for some beautifully composable code.

~~~
eru
Looks like poor man's partial application (and currying) to me?

------
morebetterer
I like the proposal. It took me 20 years to master the 10% of C++ I understand
and use. I wonder how new programmers will be able to pick up this language.

------
zyxley
I'm reminded of the language Nim, where x.f(y) and f(x,y) are both the same
thing with different syntactic sugar.

~~~
nazgul17
I thought of Nim, too. To add to your comment, Nim's "Unified Call Syntax"
recognises as the same len(x), x.len() and also x.len (this is similar to
Ruby's poetry mode, if I am not mistaken). I find it cool, but I can also
imagine reasons for which the latter would not be appreciated by the C++
community.

------
noobermin
I have to agree with x.f(y).g(z) vs g(f(x,y),z)...jquery or d3.js would be
terribly obtuse with the latter only.

But there are times when the functional notation is nicer than the OOish
style. Really, having "both" is the right answer for libraries, although it
might not always be possible. I suppose this proposal makes it easier for libs
to do "both".

Also, can someone clarify for me, even though he mentions multimethods, these
aren't really multimethods, right? Since f(x,y) becomes x.f(y), I'm assuming
there is polymorphism involving the runtime type of x, but not y too, or is
C++ getting true blue runtime multimethods?

------
mojuba
I'm curious, would C++ ever consider categories like in ObjC? I.e. class
extensions that unlike inheritance don't change the type.

It kind of gets close with the unified call: if you have a call f(x,y) then
either x.f(y) is matched or some f(x,y) that may be defined elsewhere. But
still not the same as the ability to define a new x.g(y) for an existing
class.

(Edit: I can't even start to think what implications this will have on
templates. They will probably become even more impenetrable.)

~~~
mikeash
Their proposal to allow x.f(y) to match a free function f(x, y) would obviate
the need for anything like categories. I'm puzzled and saddened that this part
of the proposal didn't go through.

------
hellofunk
>C++ provides two calling syntaxes, x.f(y) and f(x,y)

I have to shamefully admit, I have no idea what this is referring to. In the
second one, is this talking about the implicit "this" pointer that is passed
to member functions?

I've never seen or written code like the second form -- if in fact f() is
defined as taking only a single argument, I've never seen it called with 2
arguments.

My guess is that this is a discussion about the implementation of member
functions, can anyone confirm?

~~~
efaref
Under the covers, `x.f(y)` is actually something like `_ZN1X1fEi(&x, y)`,
where the mangled name indicates the class of X and the type of y. The
implicit 'this' pointer is actually explicit in the implementation. Similarly,
a call to `f(&x, y)` would actually be a call to `_Z1fP1Xi(&x, y)`, with
similar reasoning.

The author is proposing that writing

    
    
      f(x, y)
    

Which would normally only bind to the second mangled function, would bind to
the first mangled function if the second is not available.

The additional proposal, that:

    
    
      x.f(y)
    

would work in the other direction was rejected. That's a bit of a shame, as
this kind of duality is powerful in languages that support it (e.g. scala).

~~~
daemin
This is also why functions can work on a null object and not crash, as long as
they do not access instance data. Though at that stage they should be static
or external.

~~~
twoodfin
AFAIK, this is still undefined behavior, so "can work" means "you got lucky
this time, your next compiler upgrade may turn this into a crash, security
vulnerability, or utterly confounding Heisenbug".

~~~
blt
Ugh, I remember seeing null `this` values used on purpose in some old Windows
MFC program, or maybe it was COM...

I imagine the author was an old-school C programmer who understood well how
the C++ object veneer maps down to equivalent C code / assembly. From that
perspective, dealing with null `this` pointers is just another way to get the
compiler to spit out the assembly instructions you want, and maybe enables
some elegant patterns in client code... but it's exactly the kind of thing
that makes newcomers hate C++.

------
kazinator
In TXR Lisp, I provide a "referencing dot" syntax
([http://www.nongnu.org/txr/txr-
manpage.html#N-013775DB](http://www.nongnu.org/txr/txr-
manpage.html#N-013775DB)).

A dot between symbols or compound expressions, with no whitespace, translates
to a qref form:

    
    
       a.b.c.d   ->   (qref a b c d)
    

The qref macro ([http://www.nongnu.org/txr/txr-
manpage.html#N-03A212AB](http://www.nongnu.org/txr/txr-
manpage.html#N-03A212AB)) in turn does useful things like:

    
    
       obj.child-obj.slot  ;; slot access
       obj.(fun arg1 arg2) ;; (call obj.fun obj arg1 arg2), obj evaluated once
    

I didn't go for the equivalence of (f x y) and x.(f y) because it's not a
CLOS-like object system. It's single dispatch, and a method is actually a
slot. Thus x.(f y) semantically corresponds to (call x.f y), and I don't see
any value in making (f x y) a synonym for that, only harm.

It would complicate how function calls work. If global function f exists, x.(f
y) still calls the one in x, unambiguously. for (f x y) to do that, the
function lookup for f would _first_ have to check that x is a struct object,
and whether it has a slot called f, and then use it. Then otherwise fall back
on behaving like a normal function call. That's just too much semantic cruft
jammed into function calls.

The x.(f y) (f x y) equivalence only makes sense if you have a CLOS-like
object system with generic functions that are separate from classes, so that f
is a generic function named in the ordinary function namespace, which, when
called, dispatches methods. Or a completely static language in which it is
worked out at compile time whether f is an ordinary function or the member.

------
makecheck
One of the silly things about member functions is the effect that they have on
argument ordering when ordering is not _supposed_ to matter.

For example, consider "operator +", which can have in-object or function
implementations. The ridiculous thing is that, for any two types in an
expression like "+", you can't tell from observation if it will compile! Is it
only available as a member function? If so, then any primitive left-hand
argument in this supposed-to-be-commutative expression must be swapped to the
right side, or wrapped in a pointless object. The same problem can exist for
non-operator functions.

At least now with "auto" and other implicit typing (and even tuples and
std::get<needed_type>), there is a chance to write single functions that work
around messes such as these.

~~~
kibwen

      > One of the silly things about member functions is the 
      > effect that they have on argument ordering when ordering 
      > is not supposed to matter.
    

This isn't a problem with member functions, free functions have this problem
as well. And not just in C++, commutativity just isn't a first-class concept
in any programming language that I've ever seen.

~~~
makecheck
My main point is that there is _no_ way for something like "2 + object" to do
the right thing when member-function syntax is expected, whereas there is at
least a way for a free function to do so; and the language could have avoided
this problem.

I believe this is also the entire reason that recent C++ standards introduced
free-function forms of things like begin(thing)/end(thing)/etc. because it was
just too hard for something like a template to operate on a value when it
couldn't tell whether or not object syntax would work.

------
dallbee
It's worked well for a number of languages. Nim was mentioned, and D has done
this for quite some time as well. [http://dlang.org/spec/function.html#pseudo-
member](http://dlang.org/spec/function.html#pseudo-member)

------
Pxtl
I like C#'s approach where a static function can be called as a method by
adding the "this" keyword to the first parameter in its declaration. This
makes it opt-in, which is annoying for legacy code. All collections got a
fantastic map/filter/fold system bolted on through this mechanism, and as
Bjarne noted the dot notation is great for chaining calls.

So in C#, to declare an fn as an "external method" you say:

    
    
        static void MyFunc(this MyClass myObj, int someParam){doStuff();}
    

Which can be called as

    
    
       var foo = new MyClass();
       foo.MyFunc(5);
    

The downside is that method not found errors become much more complicated and
it's not obvious to the user whether a method is coming from a different
package or not.

------
T-zex
Is this somehow related to C# extension methods?
[https://msdn.microsoft.com/en-
gb/library/bb383977.aspx](https://msdn.microsoft.com/en-
gb/library/bb383977.aspx)

------
byuu
What a shocker. Allowing f(x,y) to find x.f(y) is nearly useless; and allowing
x.f(y) to find f(x,y) is scrapped. Even though D can do the latter just fine.

I can already write all my class functions as f(x,y) functions, if I want my
code to look like C.

The whole point of UFCS is to work around the obnoxious fact that classes are
closed after their initial definitions(＊). Only the latter can help with that.
So when I use a string library that has a reverse function, I can say
string.reverse(), but when it then is missing a lowercase function, my only
option is to create lowercase(string) myself. And now my usage of the string
class is a mixture of global function versus member function calls. No
consistency at all. And when I start trying to chain them, it turs into a
complete disaster, eg:

    
    
        split("\n", lowercase(string.reverse().rtrim("#"))).strip();
        //vs
        string.reverse().rtrim("#").lowercase().split("\n").strip();
    

And this is why library writers end up trying to pack the kitchen sink into
their classes. Or you end up with things like std::string that, out of the
box, are basically just glorified memcpy classes (no trim, no transform, no
string replace, no tokenization, no parsing [eg string->int], etc etc.); and
thus you end up with countless people making their own string classes anyway.

UFCS could have even allowed extending built-in types in extremely useful
ways. I could have declared "BitRef int::bits(int lo, int hi)", and allowed
for eg "int x; if(x.bit(7)) x.bits(2,3) = 2;" instead of "if(x & 0x80) { x |=
0x08; x &= ~0x04; }" \-- that may look a bit weird, but anyone who's done a
ton of bit-twiddling will certainly appreciate the potential there for cleaner
code.

I really hope D can get the GC completely out of the standard library and get
a command-line switch to disable it. Then I can seriously consider switching
languages.

(＊) we could actually solve this problem with external class functions, but
I'm sure they're even less willing to go for that. But imagine if you had:

    
    
        class Integer {
          ...
          int square() const { return x * x; }
          int self() const { return x; }
        private:
          int x;
        };
        
        extension int Integer::cube() const { return self() * square(); }
        
        int main() {
          Integer x = 5;
          print("{0}, {1}\n", x.square(), x.cube());
          //prints 25,125
        }
    

The idea would be that your extension can't access the private/protected state
(unless the function is declared a friend of the class.)

You still have the ability to extend classes with useful functions as needed,
and the encapsulation principles aren't violated: no internal state can be
accessed, the functions can't be virtual so you won't need to modify the
vtable definition, etc.

~~~
72deluxe
I know they don't have algorithms are part of the string class in C++ but
that's because you can just treat it as any other container and run functions
against it, like you would on a vector etc.

That's how I saw the string class anyway?

