Justin Bassett
2018-08-16 05:10:39 UTC
I'm working on a paper to write this up more formally, but before I do
that, I wanted to share my research and work and generate some discussion.
Currently, it is possible to pass parameters by position, but not by name.
To work around this limitation, there are a multitude of solutions. A few
are listed here:
- The "Named Parameters Idiom," sometimes called the Builder Pattern.
- Boost Parameters
<https://www.boost.org/doc/libs/1_68_0/libs/parameter/doc/html/index.html>.
Overloads operator= to make it look like a named parameter.
- Boost Graph's named parameters. Parameter values are passed via member
functions.
- Strong types. This suffers from the limitation that parameters cannot
be reordered, and sometimes you'd have to create types that otherwise have
no meaning.
- Designated Initializers. There are those who hope that designated
initializers will work as named parameters, but they don't work that well.
Each of these have disadvantages. Here's a small list:
- Hard to maintain.
- Have to prefix every argument with the library's namespace, or else
use a lot of using declarations or even a using directive.
- Each argument has to be passed in order (Strong types), otherwise it's
a ton of overloads for the library author to work out.
- Possibly worse code-gen
I claim we should standardize a way of passing named arguments in C++.
Use cases for Named Parameters (aka Named Arguments, aka Keyword Arguments):
- Named-or-positional parameters.
- Name-only parameters
- Either of the above combined with default arguments
Syntax: there are two possible syntaxes I've seen that work for named
parameters in C++:
foo(.name = value)
foo(name: value)
I honestly don't care which we go for, but for consistency with designated
initializers (which may be a bad thing if the behavior is significantly
different), I'm going to choose the foo(.name = value) syntax for the time
being.
For declaring a function with named parameters, it is imperative that it is
opt-in, otherwise, functions parameter names become part of the public API
of the function, which is undesirable:
int foo(int .x);
It might be tempting to separate the name of the parameter from the name
used in named parameters, but I claim this overly repetitive. As long as
named parameters are opt-in, having to give an alternative name is
completely redundant. (I believe Swift does this)
There should be some way to specify that the parameter is name-only. For
example, altering Python's implementation
<https://www.python.org/dev/peps/pep-3102/>:
// anything after the . is name-only
int foo(., int .x);
Default arguments should be able to work as they are now.
Semantics:
It's a pretty standard rule that all named arguments must come after all
positional arguments. It is also pretty standard that named arguments can
be in a different order than in the function declaration. I don't see a
compelling reason to break from this, even though there are exceptions in
languages today (e.g. C# and Swift)
There are some options on how the named parameters should work. It could work
like C# and just reorder the arguments
<https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/named-and-optional-arguments#overload-resolution>.
However, I believe that the name should be part of the function type and
should be mangled into the name for the function. It should not be allowed
for the following two declarations to coexist:
int foo(int .x, int .y);
int foo(int .a, int .b);
If the name of the parameters are not part of the function, then if those
declarations are in separate translation units, there's little way to
enforce that this should fail to compile / link.
This does bring in the issue that it becomes harder to change existing APIs
to accept named parameters in an ABI compatible manner. For this, maybe the
compiler could emit two symbols for the named-positional argument case?
Otherwise, I think there should be some way to either cause the compiler to
emit two symbols or otherwise maintain ABI compatibility.
Now for templates. In order to have higher-order functions which work on
named parameters, I believe it is necessary to have a new kind of template
parameter, something like this:
// I separated the name completely from the type.
// This would severely complicate generic code, however, so maybe there's
// a way to keep both together
template <typename F, .typename Name, typename Arg>
void call(F fn, Arg&& .Name arg) {
// Some syntax to expand the name is needed. I'm not sure what
// a reasonable choice is. There should also probably be a way
// to generate a name from a compile time string.
fn(.(Name) = std::forward<Arg>(arg));
}
// Or maybe something combining the name and type would be better:
template <typename F, .typename Kwarg>
void call(F fn, Kwarg&& arg) {
fn(.(nameof(Kwarg)) = std::forward<Kwarg>(arg));
}
// Valid ways to call it:
call(sqrt, .x = 9);
call(sqrt, 9); // We didn't enforce name-only in the declaration of call
Reflection could possibly allow us to inspect the actual name supplied
(useful for libraries such as fmt: fmt::format("({x}, {y})", .x = 10, .y =
20); ), and it would probably be useful to be able to generate a name from
a compile-time string.
How lookup is performed: it's certainly possible to have overloaded
functions along with named parameters. I haven't spent the time yet to
flesh out all the details, but the named parameters can be each in their
own dimension, as compared to the positional dimension.
Further ideas: I don't want to propose this yet, but I would also like to
see named parameters in template parameters. For example, it would be nice
to be easily able to specify the allocator of an unordered_map:
std::unordered_map<Key,
Value, .allocator = MyAllocator> . I believe this would break ABI
compatibility, though.
Implementation:
It sounds crazy to add a new kind of template parameter and include the
name of the parameters as part of the function type, but I believe this
would be implementable with some kind of __name<"parameter_name"> type with
some connection to the regular template type, as well as some rules on
reordering (consider something like sorting all name-only parameters
lexicographically and re-mapping named-positional parameters to their
position). I won't know for sure until I attempt an implementation, but I
believe it should work.
References - some things I've found about named parameter design, which I
may have referred to in my writeup above. Most of these I didn't link to
inline:
https://www.python.org/dev/peps/pep-3102/
https://internals.rust-lang.org/t/pre-rfc-named-arguments/3831
https://internals.rust-lang.org/t/pre-rfc-named-arguments/3831/196
https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/named-and-optional-arguments#overload-resolution
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4172.htm
http://jamboree.github.io/designator-draft.html
https://www.reddit.com/r/cpp/comments/5mdes5/what_happened_to_the_named_parameter_proposal/
that, I wanted to share my research and work and generate some discussion.
Currently, it is possible to pass parameters by position, but not by name.
To work around this limitation, there are a multitude of solutions. A few
are listed here:
- The "Named Parameters Idiom," sometimes called the Builder Pattern.
- Boost Parameters
<https://www.boost.org/doc/libs/1_68_0/libs/parameter/doc/html/index.html>.
Overloads operator= to make it look like a named parameter.
- Boost Graph's named parameters. Parameter values are passed via member
functions.
- Strong types. This suffers from the limitation that parameters cannot
be reordered, and sometimes you'd have to create types that otherwise have
no meaning.
- Designated Initializers. There are those who hope that designated
initializers will work as named parameters, but they don't work that well.
Each of these have disadvantages. Here's a small list:
- Hard to maintain.
- Have to prefix every argument with the library's namespace, or else
use a lot of using declarations or even a using directive.
- Each argument has to be passed in order (Strong types), otherwise it's
a ton of overloads for the library author to work out.
- Possibly worse code-gen
I claim we should standardize a way of passing named arguments in C++.
Use cases for Named Parameters (aka Named Arguments, aka Keyword Arguments):
- Named-or-positional parameters.
- Name-only parameters
- Either of the above combined with default arguments
Syntax: there are two possible syntaxes I've seen that work for named
parameters in C++:
foo(.name = value)
foo(name: value)
I honestly don't care which we go for, but for consistency with designated
initializers (which may be a bad thing if the behavior is significantly
different), I'm going to choose the foo(.name = value) syntax for the time
being.
For declaring a function with named parameters, it is imperative that it is
opt-in, otherwise, functions parameter names become part of the public API
of the function, which is undesirable:
int foo(int .x);
It might be tempting to separate the name of the parameter from the name
used in named parameters, but I claim this overly repetitive. As long as
named parameters are opt-in, having to give an alternative name is
completely redundant. (I believe Swift does this)
There should be some way to specify that the parameter is name-only. For
example, altering Python's implementation
<https://www.python.org/dev/peps/pep-3102/>:
// anything after the . is name-only
int foo(., int .x);
Default arguments should be able to work as they are now.
Semantics:
It's a pretty standard rule that all named arguments must come after all
positional arguments. It is also pretty standard that named arguments can
be in a different order than in the function declaration. I don't see a
compelling reason to break from this, even though there are exceptions in
languages today (e.g. C# and Swift)
There are some options on how the named parameters should work. It could work
like C# and just reorder the arguments
<https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/named-and-optional-arguments#overload-resolution>.
However, I believe that the name should be part of the function type and
should be mangled into the name for the function. It should not be allowed
for the following two declarations to coexist:
int foo(int .x, int .y);
int foo(int .a, int .b);
If the name of the parameters are not part of the function, then if those
declarations are in separate translation units, there's little way to
enforce that this should fail to compile / link.
This does bring in the issue that it becomes harder to change existing APIs
to accept named parameters in an ABI compatible manner. For this, maybe the
compiler could emit two symbols for the named-positional argument case?
Otherwise, I think there should be some way to either cause the compiler to
emit two symbols or otherwise maintain ABI compatibility.
Now for templates. In order to have higher-order functions which work on
named parameters, I believe it is necessary to have a new kind of template
parameter, something like this:
// I separated the name completely from the type.
// This would severely complicate generic code, however, so maybe there's
// a way to keep both together
template <typename F, .typename Name, typename Arg>
void call(F fn, Arg&& .Name arg) {
// Some syntax to expand the name is needed. I'm not sure what
// a reasonable choice is. There should also probably be a way
// to generate a name from a compile time string.
fn(.(Name) = std::forward<Arg>(arg));
}
// Or maybe something combining the name and type would be better:
template <typename F, .typename Kwarg>
void call(F fn, Kwarg&& arg) {
fn(.(nameof(Kwarg)) = std::forward<Kwarg>(arg));
}
// Valid ways to call it:
call(sqrt, .x = 9);
call(sqrt, 9); // We didn't enforce name-only in the declaration of call
Reflection could possibly allow us to inspect the actual name supplied
(useful for libraries such as fmt: fmt::format("({x}, {y})", .x = 10, .y =
20); ), and it would probably be useful to be able to generate a name from
a compile-time string.
How lookup is performed: it's certainly possible to have overloaded
functions along with named parameters. I haven't spent the time yet to
flesh out all the details, but the named parameters can be each in their
own dimension, as compared to the positional dimension.
Further ideas: I don't want to propose this yet, but I would also like to
see named parameters in template parameters. For example, it would be nice
to be easily able to specify the allocator of an unordered_map:
std::unordered_map<Key,
Value, .allocator = MyAllocator> . I believe this would break ABI
compatibility, though.
Implementation:
It sounds crazy to add a new kind of template parameter and include the
name of the parameters as part of the function type, but I believe this
would be implementable with some kind of __name<"parameter_name"> type with
some connection to the regular template type, as well as some rules on
reordering (consider something like sorting all name-only parameters
lexicographically and re-mapping named-positional parameters to their
position). I won't know for sure until I attempt an implementation, but I
believe it should work.
References - some things I've found about named parameter design, which I
may have referred to in my writeup above. Most of these I didn't link to
inline:
https://www.python.org/dev/peps/pep-3102/
https://internals.rust-lang.org/t/pre-rfc-named-arguments/3831
https://internals.rust-lang.org/t/pre-rfc-named-arguments/3831/196
https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/named-and-optional-arguments#overload-resolution
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4172.htm
http://jamboree.github.io/designator-draft.html
https://www.reddit.com/r/cpp/comments/5mdes5/what_happened_to_the_named_parameter_proposal/
--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/CAPuuy5eBx1ybcPjS6dNsk7n1_c2uhdRBS19qr%3Dq5cj3cTURnLQ%40mail.gmail.com.
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/CAPuuy5eBx1ybcPjS6dNsk7n1_c2uhdRBS19qr%3Dq5cj3cTURnLQ%40mail.gmail.com.