I have being working on this from a completely different angle, and will publish this to the list soon.
I want to propose named argument packs
And the motivation is not only from named arguments perspective, but more from Multiple Return Values functions
With structured binding in C++17, and P0889 for more copy elision (and my proposal in this mailing list to make these mandatory), the style of Multipl Return Function would become more popular.
There are a number of problems with it:
- Structured binding declaration allow you to forget the types you use, and if you forget the names and order of parameters as well, you're in big trouble:
```c++
struct result_t { int a; int b;};
extern result_t foo();
void bar() {
auto&&[b, a] = foo(); //Oops, wrong order, compiles fine
//...
}
```
- Problem with passimized NRVO in certain cases, which may be relied upon:
```c++
result_t foo(bool b1, bool b2, int& val) {
int i = 5; // i can't be NRVOd even though the layout of the result type is meaningless, only order of evaluation of the return expression
if (b1) i = std::move(val); //this is to prevent reordering optimization to actually allow it
if (b2) return {.a = i, .b = 3};
else return {.a = 3, .b = i};
}
```
The solution I found to be most tractable is the following two proposals:
1. Unpacking an aggregate type which is reachable but not visible (not part of the public API of the module where it is defined) must be done with the correct names and order of the type declaration.
2. Add a syntax to define anonymous non-copyable, non-movable structs with given types, names and order (but not memory layout)
And make designated initializers evaluate to an unnamed struct of this type.
This magic struct would be convertible to any struct with the same types, names, and order of parameters (which may affect overload resolution in designated initializer lists, haven't really thought of this).
This can be zero overhead if UCE (P0889) is accepted, and will must be zero overhead if my (to be written) proposal on mandatory NRVO is accepted
In total it looks like this:
```c++
//lib.cpp
export module lib;
export {int .a, int .b} foo() {
return {.a = 5, .b = 3};
}
//user.cpp
module user;
import lib;
void bar() {
// auto&&[b, a] = foo(); //Compile Error
auto&&[a, b] = foo(); //OK
auto &&[c = .a, d = .b] = foo(); //OK, can rename to prevent name collisions
auto res = foo();
auto b = res.a; //OK, I'm shooting my own leg
auto a = res.b;
//OK, I'm shooting my own leg
}
```
Now a function taking this sort of unnamed type declaration as a parameter would have named arguments as this is the only way to create objects of this type.
Note that calling this function from a different module with a designated-initializer expression evaluates the expression to an object of a different unnamed type and passes that type to the function call. Then a conversion between the unnamed type would be triggered (they may have different memory layouts).
How to prevent misuse:
1. Order Errors: Unpacking a non-exported type must be done with the correct names and arguments.
2. Misused as structs: It is non-copyable and non-movable which means it is usable in APIs only
3. DRY: It is unique within the scope it is defined. If it is defined in a function declaration, it is unique to the namespace - i.e. if you have named arguments that repeat in multiple places - you must pack them into a struct.
4. Named arguments passing: Rule 1 also applies to aggregates, which means the easiest way to pass arguments to an exported function taking non-visible aggregate is by a designated-initializer expression.
Bikeshedding the syntax a bit:
I don't want the keyword `struct` to be part of the definition as it would suggest memory layout assumptions. We don't want the memory layout to be transparent as it is used for packing and unpacking a bunch of arguments together.
What do you think of this approach?