Hacker News new | past | comments | ask | show | jobs | submit login
Implementing UFCS for C++ in Clang (dancrn.com)
51 points by foxhill on Aug 3, 2020 | hide | past | favorite | 23 comments



C++ could adopt the UFCS universal-function-call feature of D-language that allows any ordinary free function to be called as if they were methods, without any special kind of type annotation. One of major advantages of UFCS is that it enhances the discoverability of free functions in IDEs. An user could write 'Object' followed by dot '.' and the IDE could show all possible functions applicable to that type. In D-language, the UFCS also works for primitive types such as int, double and so on.

D-Language UFCS: http://ddili.org/ders/d.en/ufcs.html and https://tour.dlang.org/tour/en/gems/uniform-function-call-sy...


It also reduces the motivation to make "kitchen sink" classes. The idea is that functions that don't need access to private class members should not be member functions.


It could, but it keep getting rejected in committee votes, much to the frustration of Bjarne.


It's a shame. UFCS is one of the single biggest features that could really shave off huge amounts of boilerplate. It can also allow you to provide fine grained type safety where you may not usually be able to (as opposed to passing this down the chain a la operator<<)


> C++ could adopt the UFCS universal-function-call feature of D-language that allows any ordinary free function to be called as if they were methods, without any special kind of type annotation.

It really couldn't. It would be a breaking change.


what would it break? It litterally was proposed by an extension, with support from Stroustrup.

IIRC Stroustrup regrets using . for member function calls (as opposed to just some syntactic sugar for any function call)


The way it was worded in the GP comment would change name lookup in existing well-formed programs, making them fail to compile, or compile differently.

I'm not familiar with Stroustrup's proposal, but I doubt that he proposed it without some opt-in annotation.


A common idiom in C APIs is to pass a struct pointer as the first argument to functions (as sort of a pseudo-thispointer). It would be nice to have this extension at least simply for syntactic sugar for C interoperability.


This is exactly what 'this' is in C++, it's a pointer to the class/struct instance and the compiler inserts it as the first parameter behind the scenes. There's no difference between a class method and a class static function that takes a pointer to a class insance as its first parameter.


On another side, one can argue that the implemented "unification" is anyway just for a convenience of using a dot call instead of just passing the reference to the object as the first parameter of a "plain" function call.


I wish the article included some real-world practical example of when that would be useful because as it stands I'm very confused. Why not just make your Extension an other class that would be inherited? Or more simply, why not make it a member method to begin with?


> Or more simply, why not make it a member method to begin with?

You may not be the author/maintainer of that class.

> Why not just make your Extension an other class that would be inherited?

Because now I have a down casting issue (especially when talking about non-heap "objects"). Which means implementing and forwarding dozens of methods, and/or sloppy bug-prone (when some other developer puts variables on the class without noticing) implicit casts.

> I wish the article included some real-world practical example of when that would be useful

The classic one is interface helpers. Methods which smooth out the usage of an interface but are not relevant to the implementation of it. Yes one can put those methods on the interface's definition. But this allows separating where the interface is declared (some header some where) and where the code for it is defined (some code file), potentially across libraries (e.g. a header only "library" of interfaces, and a utility library of methods for making it nice to use). Notably this also avoids some frustrating header shenanigans and allows writing interface helpers against concrete types to come up with a more natural library organization.

The next one is it much easier to write "literate programming" style code in C++ (which is nice because they often make types "disappear" in their usage, and many argue provide more readable code as a consequence). A problem many developers currently solve with operator overload (see `<<` for the C++ standard printing library). This also makes it way way easier for such literate programming to be extensible (believe me I wrote the templates for extensible literate programming once, it's insanely painful).

Also, depending on how these implement templates, they might be a much more appreciated method of adding optional class methods to templates by making the method a compile error to call in the first place ("missing method") rather than a substitution failure error ("<massive call stack>").


You might not be the author of that class to be able to add a method to it.



Too much inheritance is widely considered bad OOP. Cross component inheritance is considered a non-non for some. (e.g. inheriting from QValueList<T>). Most OOP languages offer a "sealed" of some kind to prevent inheritance because of this.

Also these functions do not have access to private or protected fields.

It's a way to argument a existing API for your use case without too thight coupling, whatever that means.


This isn't really true in every language. Not every use of inheritance is a case of OOP. Inheritance is a mechanism used for multiple purposes (especially in C++) and OOP is merely one of them. It's also just a way to do mixins, and it's frequently used for that in C++.


Joining an iterable of strings into a single string. Python does:

  seperator.join(iterable)
  "".join(["foo", "bar", "hello"]) # => "foobarhello"
  ", ".join(["Hacker News", "Twitter"]) # => "Hacker News, Twitter
Many people have complained about this strange syntax:

https://mail.python.org/archives/list/python-ideas@python.or... https://mail.python.org/archives/list/python-ideas@python.or... https://mail.python.org/archives/list/python-ideas@python.or... https://mail.python.org/archives/list/python-ideas@python.or...

`["a", "b", "c"].join(iterable)` was rejected because it forces every iterable to implement iterable functions.

Additionally some iterables, like generators, are mutated when you iterate over them:

  ", ".join(streaming_api.fetch_names())
UFCS let's you write join as a free function and still use "".join(iterable)

NaN guarding:

  import std.traits;
  auto unwrap(T)(T input) if (isFloatingPoint!T) {
      // Asserts cost nothing when turned off with -check=assert=off
      // The compiler can fully inline this function, so it isn't expensive
      assert(input !== input.nan);
      return input;
  }
Use like this:

  if (objSize.unwrap < cellSize) {
     // Allocate a new cell for the object
  } else {
     // Insert into current cell
  }
The parentheses are omitted because Dlang has https://dlang.org/spec/function.html#optional-parenthesis. The above compiles into this:

  if (objSize.unwrap() < cellSize) {
     // Allocate a new cell for the object
  } else {
     // Insert into current cell
  }


on a side note, if you can make extensions for clang that provide new syntax, I wonder to what extent Qt's moc could be turned into a clang extension.

edit: looks like some people are trying - https://github.com/woboq/moc-ng


> The example provided doesn’t have much benefit over the existing free-form call (i.e. Extensions.GetValue(what, "nothing") - and this still is also a valid way of calling that method).

Extensions.GetValue ?!


This acryonym never made sense here or for Rust (where I think the name came from>?). "call" means function applications, but the vast majority of the text is on function declarations.

Fine if new ways of calling things stem from new ways of declaring things, but then let's please name the feature after the root change rather than it's ramifications!

--------

On a different note. I am very excited for C++ to ape Rust features. Please keep doing it. The end result ought to be a lower marginal cost of Clang implementing Rust, in which case we can get really good C++ <-> Rust FFI....and migrate away from C++.

Please, build a bridge from the sinking island.


> I am very excited for C++ to ape Rust features.

lol, UFCS was discussed in C++ before Rust even existed. Here's a paper from 2003 that mentions it: https://www.stroustrup.com/N1522-concept-criteria.pdf

the committee is pretty much against it so it won't get into "C++" though. but who cares, clang as a compiler already targets more platforms and has more eyeballs and development than most other languages anyways.


It's called "uniform function call syntax" because it comes from D unifies function calling between member functions and free functions. It's not called "uniform function declaration syntax" because the declarations are obviously not unified. The fact that you have to declare functions shouldn't be relevant to the name.

And of course rust people found a way to make this all about rust. I highly doubt the Clang devs are thinking "hmm we're really close to compiling rust but we just can't figure out UFCS." And C++ is not at all a sinking island.


It looks like the D version doesn't have a `this` parameter or similar, so this version is absolutely Rust or Python or something inspired. On the other hand that means for D it was named correctly.

I have recently been full time writing C++ and yes it is a sinking island. Don't worry, I assume just in time for C++ to migrate to Rust, Rust will also start sinking (and not because of the influx of C++).




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: