Named function arguments
- Document number:
- D3777R0
- Date:
2025-09-14 - Audience:
- EWG
- Project:
- ISO/IEC 14882 Programming Languages — C++, ISO/IEC JTC1/SC22/WG21
- Reply-to:
- Jan Schultke <janschultke@gmail.com>
Murat Can Çağrı <cancagri.dev@gmail.com>
Matthias Wippich <mfwippich@gmail.com>
Lénárd Szolnoki <cpp@lenardszolnoki.com> - GitHub Issue:
- wg21.link/P3777/github
- Source:
- github.com/eisenwave/cpp-proposals/blob/master/src/named-args.cow
Contents
Introduction
Why not just use a struct and designated initializers?
No templates
Worse overload resolution
Complexity and inconvenience
Dilemma of choice
ABI and performance considerations
Freezing API
Other workarounds
Prior Art
N4172 "Named arguments"
P0671R2 "Self-explanatory function arguments"
P1229R0 "Labelled Parameters"
Motivation
Simplifying overload resolution
Improving error messages
Preventing common bugs
Avoiding magic numbers
Note on encouraging large parameter lists
Arbitrary keyword arguments
Design
Overload resolution impact
Mixing named and positional arguments
Forwarding named arguments
[[positional]]
Impact on the standard
Integration into the standard library
Implementation experience
Wording
References
1. Introduction
We propose a syntax for calling C++ functions with named arguments:
There is no consensus yet on whether changes to C++ function declarations are needed to opt into this call syntax.
A call syntax like this has been suggested multiple times historically in [N4172], [P0671R2], and [P1229R0], but never in a way that found consensus in WG21. Nonetheless, this shows that there is interest in the feature, and perhaps, if approached the right way, the idea could succeed.
1.1. Why not just use a struct and designated initializers?
A common criticism of the idea is that rather than using named arguments,
one could use a .
While this seemingly solves the issue, upon further thought, this is not a good solution for the following reasons:
1.1.1. No templates
The approach cannot be used in a template like
,
or at least, a call like would be necessary.
To be fair, this problem may be solved by permitting CTAD for function parameters,
for some cases.
It does not work for forwarding references.
However, even if that approach worked,
it would require the user to create really complicated class templates
for parameter sets like the ones we have in .
1.1.2. Worse overload resolution
With the proposed semantics,
overloads are eliminated as non-viable candidates extremely early,
on the basis of not having matching parameter names.
For the approach,
when an overload cannot be called due to a name mismatch,
this happens at a later stage:
when forming implicit conversion sequences.
1.1.3. Complexity and inconvenience
Users don't want to maintain these additional parameter s.
They make a simple problem of passing named arguments more tedious than it needs to be.
Realistically, if someone proposed to add an overload to
that took , they would be laughed out of LEWG.
While the existing declaration is obviously problematic,
it would be a huge burden
on the committee to maintain these additional argument s.
Library authors in general face the same problem.
1.1.4. Dilemma of choice
Even when designing new functions from scratch,
it feels unnecessary and much more complex to pass all arguments via
rather than simply writing a function.
In a code base where passing arguments via is common,
one constantly has to make the choice whether to pass normally, via ,
or perhaps both.
This adds up to wasted developer time.
Named arguments let us have our cake and eat it too.
1.1.5. ABI and performance considerations
In most ABIs, when a is sufficiently large,
it will not be passed by register.
Inlining can sometimes solve this issue,
but especially for larger mathematical functions,
it should not be relied upon,
and these ABI issues can result in degraded performance.
The second overload is not only clunkier to write,
it also results in worse
This problem is extremely common, not specific to the x86-64 ABI.
To name one other example,
in the Basic C ABI for WebAssembly,
would compile to parameters
whereas would be passed indirectly, i.e. via memory address.
These ABI problems make the prospect of using a
extremely unattractive for high-performance mathematical functions.
This is especially unfortunate considering that functions such as
and could significantly benefit from
argument names at the call site.
1.1.6. Freezing API
With as specified as above,
the user is able to store and pass around ,
meaning that any change to it, such as turning it into a class template
could break API and ABI.
In general, creating a for a set of parameters freezes that parameter set.
This would be unacceptable in e.g. the C++ standard library.
The functions specified there are deliberately stated to be "non-addressable"
so that changes to overload sets can be made.
For example, a single function in stated to take an iterator
could actually be implemented using two separate overloads:
one for forward iterators and one for random access iterators.
1.2. Other workarounds
While creating a is most commonly cited as a workaround,
other options to provide named arguments are sometimes mentioned:
In short, Fluent Interfaces
use member function chaining so that each argument is provided individually
to some kind of "builder class".
The parameter types technique involves various parameter-specific types
rather than working with scalar values,
which often makes misuse of functions less likely thanks to the type system.
These workarounds share most of the problems described in §1.1. Why not just use a and parameters of
in a class does make it unlikely that the function is misused,
and it's relatively lightweight.
As good as they sometimes are,
these workarounds only chip away at the massive problems we have.
For example, to prevent misuses of the constructor,
are we seriously proposing to write code like this in the future?
Should C++ developers generally be expected to design their APIs like this? In practice, neither LEWG nor library developers at large want this. It takes an absurd amount of API design effort, extra written code, and slower compilation. A side-by-side comparison should make it obvious why these techniques are undesirable:
| Parameter types | Named arguments |
|---|---|
|
|
|
, , , etc.
With those details involved,
parameter types become even less attractive because the technique would require
creating a large amount of class templates and relying on CTAD
rather than working with simple s.
2. Prior Art
2.1. N4172 "Named arguments"
[N4172] proposed the following syntax:
Functionally, our proposal is almost identical to [N4172], with a few key differences:
- We use the designated initializer syntax
.. top = 10 - N4172 forfeits the possibility of perfect forwarding because the authors "are not aware of a way to make this work without making parameter names part of a function's type". We have a plausible solution, described in §4.3. Forwarding named arguments.
The proposal received overwhelmingly negative feedback, with the following poll being taken:
Poll: Should we encourage Botond to continue work on this proposal? Yes=6, No=11.
The major criticism were:
- Large amount of technical work "without any new functionality".
- Encouraging large parameter lists.
- Consistency problems for parameter names.
- Parameter names becoming part of the API.
We believe that our proposal meaningfully addresses all of these criticisms. The language has evolved significantly since 2025, and the perspective on many of these issues changed.
2.2. P0671R2 "Self-explanatory function arguments"
[P0671R2] proposes the following syntax:
While the R2 proposal is strikingly similar to what we have (with different argument syntax though), R2 was never seen by the committee, and to our knowledge, it "died" in SG17.
However, R0 was seen in EWG at Toronto 2017, with largely negative feedback;
see [MinutesP0671R0].
R0 has a syntax such as which is incompatible
with existing declarations, so it is unclear how relevant the negative feedback still is.
Much of the feedback revolved around API design and possible library workarounds,
something that we address in great detail in §1. Introduction.
2.3. P1229R0 "Labelled Parameters"
[P1229R0] takes a dramatically different approach to the previous parameters. In essence, it adds the syntax:
This syntax is sugar, and expands to the following underlying code:
Unsurprisingly, this idea did not find consensus. The paper was seen in SG17 at San Diego 2018 with negative feedback; see [MinutesP1229R0] (though we don't have record of polls):
Anon: (voted against seeing the paper again) I want the feature, but I want to use it everywhere, so I'm uneasy with functions having to take
. I want a more intrusive language featurestd :: label Anon: (voted against seeing the paper again) I want to see a more full-fledged feature for this. This paper seems like a half-measure. I want to be able to reorder, have optional parameters, etc.
Anon: (voted against seeing the paper again) this has extensive impacts on the type system, ADL, overloading, etc. These are complicated parts of the language. It introduces identifiers in new places, and they leak out of the function scope. These things weren't addressed in the paper, and I would want to see extensive exploration of these things.
We see further problems with [P1229R0]:
- There is no "upgrade path" that the C++ standard library could reasonably take to support this new syntax.
- Crucial benefits of our proposal such as §3.1. Simplifying overload resolution cannot function as a quasi-library solution.
-
The proposed
syntax is asymmetrical with designated initializers, which is now an obvious problem from a C++20 perspective.label : arg
3. Motivation
3.1. Simplifying overload resolution
Named arguments can drastically simplify overload resolution by disqualifying (almost) all candidates on the basis of name mismatch.
with named arguments,
we can disqualify all but one overload based on name mismatch:
By comparison, if we make a positional call with the three arguments, we have many potential candidates:
#1, #3, and #4 are only disqualified once template argument deduction
has taken place;
they are not viable due to constraints such as
not being satisfied.
Named calls can completely bypass complex processes
such as template argument deduction,
constraint satisfaction checks,
or comparison of implicit conversion sequences when names don't match.
This is expected to significantly improve compilation speed,
especially for large and complex overload sets like the ones in .
is not part of this proposal,
but the design allows for such support to be added subsequently without breaking changes.
The long-term plan for the standard library should be to add
named argument support wherever useful.
3.2. Improving error messages
Named calls are also tremendously useful when the user makes a mistake in calling a function in an overload set. Rather than requiring lengthy explanations about unsatisfied constraints or implicit conversions sequences that cannot be formed, the error message can simply say that a call was not possible due to argument and parameter name mismatches.
overload in the following sample:
Clang 21 (similar to EDG 6.7) outputs the following error:
The constructor the user actually wanted to call is found at the very end of the error message,
buried under irrelevant explanations relating to why there is no viable conversion
from to in order to call the copy constructor, etc.
GCC 15 is even worse
because it does not even mention the constructor the user intended to call;
it speculates that we tried to call , which is wrong:
MSVC 19.43 combines the worst of Clang and GCC; it lists every constructor except the relevant one:
The diagnostic quality for named calls is expected to be better:
This could hypothetically give us the following output:
The problem in the example is not simply a quality-of-implementation issue;
the compiler cannot magically guess which constructor we intended to call.
Attempting initialization with the argument could be an attempt
at calling pretty much any constructor,
and every compiler handles this situation in a uniquely broken way.
When we make calls with named arguments, the compiler can take a much better guess at which constructor we meant to call (presumably, the one with matching parameter names). This meaningfully addresses one of the greatest and longest-standing complaints that C++ users have: overly long and complicated errors.
3.3. Preventing common bugs
or
are extremely easy to misuse with positional arguments:
While the order of parameters used in the C++ standard library
is the most common convention,
there is no universal rule for the signature of a function,
leading to this potential mistake.
Confusingly, the "range arguments" are provided last for
but first for ,
so the standard library is not even internally consistent about these things.
The same mistake is impossible with a named call:
and are by no means the only examples
of standard library functions where such mistakes can be made.
This calls the constructor,
resulting in 97 U+0010 END OF LINE characters being printed.
Such a mistake is totally preventable:
Another possible source of mistakes lies in the fact that there are two competing conventions for how to order inputs and outputs in function signatures:
-
Output first.
For example,
,memcpy ,operator << ( ostream & , /* ... */ ) ,getline ,exchange , etc.to_chars -
Output last.
For example,
,copy ,partial_sort_copy ,operator >> ( ostream & , /* ... */ ) , etc.fwrite
This inconsistency can easily result in mistakes,
such as mixing up the source and destination buffer of
because the user expected it to work like .
These mistakes are generally impossible with named arguments
because the position of the source and destination parameters are irrelevant.
3.4. Avoiding magic numbers
Named arguments can significantly improve the readability of a function call.
The meaning of the arguments is clearer with names provided:
Users have come up with variety of workarounds to avoid magic numbers/flags:
- Creating separate variables such as
.constexpr bool kerning = true -
Using ClangTidy's
bugprone-argument-comment check and writing./* kerning= */ true -
Creating wrapper types like
and providing aenum class Kerning : bool { no , yes } argument.Kerning :: yes -
Creating
aggregates and callingstruct Options .draw_text ( { /* ... */ . kerning = false } )
While all of these workarounds technically solve the problem, there is no clear answer to which of these is best, resulting in competing styles and dilemmas of choice. Named arguments would largely obsolete these workarounds.
At some large amount of parameters,
users typically create s to bundle up options,
but even then, it's not obvious whether every parameter should be in that
or if some could remain outside (e.g. a parameter).
It is also not obvious how to handle smaller parameter lists of ≥ 3 parameters.
Just three parameters can be mentally demanding, and are not usually enough to warrant
a separate .
3.4.1. Note on encouraging large parameter lists
Some people may argue that named arguments would encourage users to write
functions with overly many parameters.
We argue that the amount of parameters to a program's subroutine is innate.
Whether parameters are provided as function parameters, a ,
options for a builder class, or something else,
the amount of parameters remains the same;
text rendering doesn't become any simpler just because the font size is in a .
Parameters can only be shuffled into different places,
and there is no inherent reason why a function parameter list
would be a bad place compared to all these other alternatives.
3.5. Arbitrary keyword arguments
There are certain use cases where we would want to support a large set of "flags", "attributes", or "keyword arguments" in our functions. This is clearly illustrated by proposals such as [P2019R8], which proposes the following syntax:
Since the set of supported "thread attributes" may change in the future
and may include implementation-defined attributes,
we cannot simply use a aggregate that bundles up the attributes.
Otherwise, future changes would be an ABI break.
With named arguments, and with the hypothetical syntax in §4.3. Forwarding named arguments (not yet proposed), we could solve this issue as follows:
This exact syntax may not actually be feasible or desirable, but the principle should be clear: arbitrary keyword arguments are much more concise and natural than creating a distinct type for each "attribute".
4. Design
4.1. Overload resolution impact
In short, C++ function calls are resolved in the following stages:
- name lookup
- collecting viable candidates
- selecting the best viable candidate using various tie breakers
Named arguments are resolved during the second stage, i.e. when collecting viable candidates. Essentially, argument names are just a means of matching arguments to parameters. Positional calls do this by matching arguments to parameters from left to right, named calls do it by name. Whether a call uses positional or named arguments is irrelevant to which overload is better.
4.2. Mixing named and positional arguments
We propose a heavily restricted form of mixing name and positional arguments. Namely, all positional arguments have to come first:
This is useful because there are often tag parameters at the start, or parameters whose names are either obvious or irrelevant.
4.3. Forwarding named arguments
Past proposals have faced concerns over the ability to forward argument names, such as in:
While the following syntax is not proposed, it is possible to allow forwarding in the future, with mechanics like:
Now that C++26 has reflection, there seems to be a realistic path to forwarding argument names. While the syntax is obviously up for debate, the basic idea is:
-
"
" deduces a pack of argument names asargs . names . Note that this cannot be done withstd :: meta :: info because it is not a structural type, and even if it was, it is unclear what the pointer inside would point to. "std :: string_view " could also work bidirectionally, i.e. it could be used to specify the parameter names separately.args . names -
"
" performs a pack expansion of an argument name slice. In other words, each of the names in. [ : names : ] ... would be used as an argument name when callingnames 's constructor.T
If it was possible to deduce argument names,
this would also enable support for named arguments in :
4.4. [[positional]]
While library authors in C++ don't feel comfortable with making a parameter name a permanent part of the API, experience from other languages with named argument support (e.g. Kotlin) tells us that "parameter name permanence" is largely a non-issue. It is rare that a parameter name would have to be changed.
Nonetheless, there are certain instances where parameter name stability
cannot be guaranteed,
and to cover this case,
we propose a attribute,
which may be applied to a function declaration.
This attribute enables a warning if a function is called with named arguments:
Such an attribute is particularly useful for functions where only positional calls make sense.
Because we "disallow" named arguments, we retain the freedom to turn this into a variadic function template later:
That being said,
calling functions with named arguments is dubious in the first place,
and it is unlikely that much — or any code would be broken by changing .
5. Impact on the standard
In summary, a few parts of the core language are affected, such as function call syntax and overload resolution. The standard library impact is minimal because named argument support is not enabled by this paper. We merely add some policies in library wording.
5.1. Integration into the standard library
None of the proposals in §2. Prior Art discuss how named arguments could be integrated into the C++ standard library, despite this being an extremely important point of motivation and an important design aspect. There are a few key observations:
- When named arguments may be used, parameter names are effectively part of the API, and changing parameter names is a breaking change.
-
Standard library implementations typically use reserved identifiers such as
instead of__x for parameter names in order to avoid conflicts with macros defined by the user.x - None of the functions in the standard were standardized with "normative parameters". LEWG never discussed or approved these parameter names as a language feature, they are all editorial.
While allowing named arguments in the standard library is a long-term goal, we do not yet propose it here. Instead, we propose a general policy that the function parameter specified in the standard library are not stable and may be different in the implementation. This approach has precedent for SFINAE/expression-validity testing, for reflecting on standard library declarations, and other issues.
Subsequent proposals would gradually enable named arguments on a per-header basis.
For headers that are "named-argument-enabled",
would be specified on the few functions that
require parameter name instability.
While named arguments are not always useful (e.g. what's the point of ?),
they are rarely harmful
and rely on a parameter name that is likely to change,
especially in a document as stable as the C++ standard.
Any function that has reserved identifiers as parameter names (e.g. )
is recommended to behave as if was implicitly applied to it,
meaning that to implement this proposal,
no standard library code would need to be changed.
6. Implementation experience
See [ClangFork] for an experimental implementation.
7. Wording
None yet. Will be provided in a future revision once some design aspects have settled.