Description
Motivation
An out
parameter works as follows:
- The caller passes a pointer to a memory location.
- The callee must treat the memory location as uninitialized, and must initialize it before the function returns.
This is a very useful pattern, but notably, this is very similar to how return values already work in most ABIs. (In most ABIs, whenever a return type requires more than a few registers, the caller must provide a pointer to the memory location that the return value should be written to.)
Consequently, I propose making a slight tweak to out
parameters to unify them with the notion of return values. The end result would be that cpp2 has one fewer concept, without sacrificing expressive power or performance. In fact, it will likely make cpp2 programs more performant, because the proposed solution implies NRVO.
The proposal
In today's design, out
parameters look like this:
foo: (out s1: std::string, out s2: std::string) = {
s1 = "hello";
s2 = "world";
}
main: () = {
s1: std::string;
s2: std::string;
foo(s1, s2);
}
I am proposing to write them like this instead:
foo: () -> (s1: std::string, s2: std::string) = {
s1 = "hello";
s2 = "world";
}
main: () = {
s1: std::string;
s2: std::string;
s1, s2 = foo();
}
The differences are:
out
parameters are moved to the right side of the->
. In other words, they are treated as part of the return type.- The caller uses the syntax
s1, s2 = foo()
, instead offoo(s1, s2)
.
But notably, the compilation strategy remains the same:
- Small return values are returned via registers.
- Large return values are written directly to a memory location provided by the caller.
Basically, what we end up with is an intuitive syntax for guaranteed NRVO. Accordingly, it should be possible to return immovable types (e.g. std::atomic) via this approach. (Because in the style of C++17, we're not "optimizing away" a move. Instead, we're saying that no moves are required.) Ultimately, this is equivalent to out
parameters—all we've done is change the syntax.
The likely benefits of this approach include:
- Better composability. With return values, it is possible to nest function calls (e.g.
g(h(...), j(...))
), but without
parameters (where functions returnvoid
) it is not. - Fewer concepts. We wouldn't need
out
parameters. Instead, we would just need to allow return values to be given names. (Indeed, cpp2 already has syntax for this.) - C++ would finally have the equivalent of "guaranteed NRVO"—a useful performance optimization.