Skip to content

[SUGGESTION] Named return values can possibly make out parameters redundant #540

Closed
@nmsmith

Description

@nmsmith

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 of foo(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 with out parameters (where functions return void) 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.

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions