C++ unified call syntax: x.f(y) vs. f(x,y) [pdf]
isocpp.org
isocpp.org
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.
There, I said it.
Exceptions are a good example of that: you can't use a library that's built around exceptions for error reporting if you don't want to use exceptions in your own code.
Not to mention one more point to argue over coding style.
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.
If the language is not complex, then it is the eco-system.
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.
C++ is a classical example of this approach, every time C++ committee fights warts it has created, it is perceived like a "cool new feature".
What's exactly cool about this syntax? Is it that C++ can now mimic syntax of truly object-oriented languages better? What's the point if nothing has changed under the hood?
As mentioned elsewhere, consider exception handling, or use of Boost or STL--in many cases, libraries will leak such implementation details during stack traces or interfaces or what have you.
From a coding standpoint, using the hottest new sexy library can mean breaking compatibility with older compilers. This might be surprising, but a lot of times shops still use old versions of compilers because they're a known evil.
For all these cool new features, consider that C++ has yet to standardize on a standard library file system interface (though one is, kinda, in the works). It only just introduced hash maps into the core standard library.
It still does not deign to handle time in a useful fashion: Javascript gets this right out of the box, whereas C++ doesn't even have a way of parsing or generating an ISO-8601 datetime string without having to pull in Boost.
~
The language seems to have abandoned all of the programmers that need to get actual work done, which is fine, because they've all moved to more productive languages like Java, Python, Ruby, Javascript, Go, or PHP.
What we've always had is two syntaxes that results in pretty much the same code (the same for non-virtual member functions and freestanding functions, and slightly different for virtual member functions) that have mainly different by lookup.
This proposal is basically suggesting to reduce the gap by making the "dot syntax" closer to a superset of the function call lookup.
I don't see how that is any more confusing than any number of basic C++ semantics, and it has some fairly substantial benefits, in that it e.g. allows people to write libraries to "extend" the apparent interface of a class without forcing users to use two entirely different syntaxes.
Just being able to turn instances of baz(bar(foo(ob))) into ob.foo().bar().baz() would be worth it to me.
That's funny because I thought he was trying to suggest nobody use the . notation any more, while you give a nice example of going the other way.
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".
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.
Oh, you mean Rust (programming language)?
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!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.
Viewed from that angle, this proposal makes C++ more consistent, not less: it breaks a needless dependency between syntax and semantics.
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 :-)
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.
First, I like Herb Sutter's propsal more ( 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++.
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.
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.
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.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
Most importantly, there is no magic going on with namespaces. Method 'm' still belongs to its class's namespace.
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.
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..."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)."
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.
Bjarne has wanted multi-methods for decades.
http://www.drdobbs.com/cpp/how-non-member-functions-improve-...
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.
In fact all code should be in a database, not a file, so changes can be tracked and attributed properly. So a tool can distinguish between 'lines being edited' and 'one function deleted and another added'. And so on.
So what organizing principle is left, since files are largely invisible during the coding process? Well, classes spring to mind.
There will be very few classes in your project if you decide to prefer non member non friend functions as suggested here.
The question being, whether you go along with that suggestion or not?
To make it clear: I favor factoring APIs into classes.
It's strange that C++ luminaries like Stepanov, Meyers and Stroustrup favour non member functions.
But, for instance, `sort` is a good candidate to have outside of a class - and it actually is a global function - std::sort.
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.
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.
Unless you're a fan of `friend` keyword ;)
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.
I'm pretty sure that's not what Bjarne has in mind, though.
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.
[1] https://mail.mozilla.org/pipermail/rust-dev/2013-October/006...