5 ms·
C++ unified call syntax: x.f(y) vs. f(x,y) [pdf]
- lokedhs 12y agoOh 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 12y agoI 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?
- sagichmal 12y agoA good language is not one which maximizes the number of "cool new features", but rather one in which all features interoperate coherently.
- pjmlp 12y agoThere aren't that many examples of such languages that survived in the mainstream.
- golemotron 12y agoRuby and Python seem relatively coherent next to the feature creep of C++, Java, and C#.
- pjmlp 12y agoI cannot speak for Ruby, but Python has had big ride since the 1.0 days, specially if you take the standard library into account.
- angersock 12y agoC, Javascript. There, I said it.
- nly 12y agoC has added few features worth a damn in 15-25 years. It hasn't even bothered to augment the standard library, which is just dire in terms of API design, capability and performance. Some of C++s worst stdlib components are modelled on libc (e.g. streams and locales)
- delroth 12y agoIt fragments the ecosystem. A developer that does not want to use a feature gets limited in its choice of libraries. It makes it harder for people to contribute to other C++ projects (experience can not be directly translated since different aspects of the language might be used). 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.
- truncate 12y agoAlso, unless I have strong feeling against some feature I may spend unnecessary time thinking what syntax style should I be using rather than more important stuff. I avoid it, but it still happens to me sometimes unconsciously and I hate it. Not to mention one more point to argue over coding style.
- golemotron 12y agoThe 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 12y agoYes 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 12y agoYes, but on balance, I think that ecosystem complexity is both more tractable and addressable than language complexity.
- pjmlp 12y agoI 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.
- pjc50 12y agoDo you get to prevent your co-workers using it?
- _1 12y agoMost people don't code in a vacuum.
- dmytrish 12y agoThere's a saying, "If you want someone to feel good, hurt him and then stop". 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?
- angersock 12y agoNo, no, you really can't. 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.
- vidarh 12y agoConsider that someob.foo(other args) in C++ is already effectively syntactic sugar for <address of foo as function pointer>(someob, other args) where <address of foo> is either obtained by statically resolving which function "foo" refers to, or dynamically looking it up in "someob"'s vtable with an indirection. 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.
- phkahler 12y ago>> 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.
- tomp 12y agoI'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...
- eru 12y agoWhy is dynamic typing a feature of high level languages? I know quite a few high level languages that are statically typed. And what does C have to do with C++?
- tomp 12y agoIt's not a necessary feature of high-level languages, but you won't find it in a low-level language. The same with all other features I've mentioned.
- vidarh 12y agoI'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 12y agoMany 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/006034.html https://mail.mozilla.org/pipermail/rust-dev/2013-October/006...
- nly 12y agoEven 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 12y agoIn 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 12y agoApple'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.
- pmontra 12y agoFirst 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 12y agoIn 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.
- oleganza 12y agoStroustroup 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 12y agoI'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.
- nly 12y ago> I'd also prefer "intersect(s1, s2)" if C++ had multimethods... Bjarne has wanted multi-methods for decades.
- throwawayaway 12y agoThe 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-encapsu/184401197 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.
- JoeAltmaier 12y agoThis idea that 'files' are an organizing principle is bankrupt. In an editor I don't even know what file a method is in. Including header files is an annoyance, and should be automated. 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.
- vidarh 12y agoThe 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 12y ago>> 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.
- os_ 12y agoThe problem is that such an effort would produce a cute academic toy with zero traction in the real world. Or looking at it from the other perspective - would you expect that Clang, Chrome, Mozilla folks just rewrite their projects? What about the core infrastructure code that Google/Bloomberg have implemented in C++?
- 12y ago
- toolslive 12y agoI 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 12y agoThis 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 12y agoConsider 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.
- saucetenuto 12y agoI don't agree. The _signature_ should indicate whether a function has access to a class's internals or not, but the caller shouldn't have to care. This is already true in C++ today: when we write f(x), f might be a normal free function or it might be a friend of x. Viewed from that angle, this proposal makes C++ more consistent, not less: it breaks a needless dependency between syntax and semantics.
- KyleSanderson 12y agoThe 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.
- lelf 12y agoNote: it's just a proposal. (And I'd say highly unlikely to be ratified: could break things, don't bring anything.)
- bodyfour 12y agoI 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 12y agoThe 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...
- Heliosmaster 12y agoNot sure if I like this. Smells like the Greenspun's tenth rule all over the place.
- byuu 12y agoOh, 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++.
- CountHackulus 12y agoConsidering 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.
- ska 12y ago... 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 12y agoWhy 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 12y agoInteresting, thanks. Color me much less sure then.
- ANTSANTS 12y agoAnd so C++ takes another step towards Lisp.
- adrusi 12y agoI 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.