Named function arguments
- Document number:
- D3777R0
- Date:
2026-04-06 - 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>
Ville Voutilainen <ville.voutilainen@gmail.com>
Bengt Gustafsson <bengt.gustafsson@beamways.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
Why require an explicit opt-in?
Mixing named and positional arguments
Forwarding named arguments
Restrictions on labeled types
Combining references and labeled types
Calling labeled parameters with positional arguments
Macro-friendliness
Out-of-order named arguments
Disqualifying overloads early
C compatibility
Cross-version compatibility
Impact on the standard
Integration into the standard library
Implementation experience
Wording
References
1. Introduction
We propose a syntax for declaring C++ functions with named parameters and for calling C++ functions with named arguments:
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, proposing to add an overload to
that took would likely not be fruitful.
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 2014, 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
To cornerstone of the design is the introduction of labeled parameters,
which are parameters of labeled type.
This makes parameter types with a label a distinct fundamental type
that is tagged
or labelled
with the desired argument name.
has a single parameter of type
.
with label
Similar to reference parameters, there is a mismatch between the type of the parameter and the type of the expression in the function:
This kind of type adjustment is crucial because otherwise
access to would have type
as if it was a named argument rather than
just an access of the parameter's value.
4.1. Why require an explicit opt-in?
An obvious question is why an explicit opt-in should be necessary at all. After all, many other languages such as Kotlin allow any function parameter to be called with both positional and named syntax. However, we chose not to pursue this for a number of reasons:
- The massive amount of existing C++ code would all inadvertently opt into being called with named arguments. Parameter names in libraries were historically inconsequential and something that the library author can change arbitrarily. Even in C++26, the only way to observe parameter names is using reflection. It would be problematic to make the entire ecosystem shift towards parameter names becoming part of the API.
-
After long discussion,
we were unable to come up with an elegant solution to forwarding
for parameter names.
By comparison, our current design allows any existing
function to work with named parameters.emplace - If parameter names are not part of function type, detecting problems such as inconsistent parameters across TUs becomes difficult. If they are part of the type, that type is mangled.
-
Function pointers or features such as
could not interoperate without an explicit opt in. Our approach allows forstd :: function_ref .std :: function_ref < int ( int . x ) >
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.
This is something that works out of the box
with our design
because parameter names simply become part of the type.
Note that the type adjustment which would discard
the label from the type
only takes place parameters where the label is not dependent.
Perfect forwarding works because it is possible
to form a reference to an object of labeled type such as ,
and object of labeled type can be copied and passed around following
all the usual language rules.
Here, the type adjustment does not take place for
because the label depends on a template parameter.
However, type adjustment takes place for .
Type adjustment also takes place for
because while the type is dependent,
the label of the type is not (it is always ).
4.4. Restrictions on labeled types
Labeled types such as are copyable,
but not assignable.
There is also no implicit conversion to their underlying type
because this would drop the argument name,
which violates user intent.
4.5. Combining references and labeled types
It is both possible to have a labeled reference and a reference to a labeled type:
-
is anint . & x lvalue reference to:
with labelint x -
is anint & . x lvalue reference to
, with labelint x
Being able to form references to labeled types is crucial to enable perfect forwarding and to make labeled types not behave specially in generic code. Being able to form labeled references is necessary to have labeled parameters of reference type.
4.6. Calling labeled parameters with positional arguments
It is possible to call a function with a labeled parameter using a positional argument. This is a hugely important design aspect because
- it provides libraries an upgrade path from positional-only parameters to labeled parameters without breaking API, and
- there are many cases where providing a named argument does not add clarity.
The reason why works
is that an implicit conversion sequence from
(which is a prvalue of type ) to is formed.
This is treated like an identity conversion sequence
for the purpose of overload resolution.
Such a labeling conversion
preserves the value of the expression and
value category exactly,
so it only exists on paper,
much like a conversion from to .
Crucially, an argument of labeled type cannot be converted to another labeled type because this would alter the name against the user's intent. It also cannot be converted to its underlying type.
4.7. Macro-friendliness
A potential issue with the syntax
is that the identifier can no longer be used as the name of a macro.
When a large library opts into named parameters in large amounts of code,
this could have some chance of conflicting with macros defined by the user.
The standard library uses reserved names like or
to avoid this conflict.
To solve this problem, we propose an alias template that enables spelling a labeled parameter using a string literal:
While it would also be possible to have a builtin syntax like
rather than ,
this whole problem is relatively minor anyway,
and adding a second competing syntax appears excessive.
Most C++ users happily use identifiers like
without needing to protect against macros.
4.8. Out-of-order named arguments
We also propose to allow for reordering of named arguments to fit the respective parameters.
This requires altering overload resolution so that named arguments can be mapped onto their respective parameters, prior to forming any implicit conversion sequence and even prior to template argument deduction.
4.9. Disqualifying overloads early
As explained in §3.1. Simplifying overload resolution, cutting down the number of candidate overloads via argument names is an important design goal. To do this, the disqualification would have to occur early in the process, even before template argument deduction.
A notably feature is that template argument deduction does not take place
for ;
the entire candidate is disqualified because the argument is of a labeled type
whose label does not match any parameter label in .
and in
but not in which deduced to .
4.10. C compatibility
Surprisingly, even with the explicit opt-in syntax, it is possible to achieve C compatibility.
C++ users can call using both named and positional arguments,
whereas C users can only call using positional arguments.
This approach relies on the fact that
disables mangling of the parameter names,
so the underlying symbol is called .
It also relies on the fact that a labeled type
has the same value ABI properties as its underlying type,
similar to how has the same ABI as .
The design of the proposal can also be adopted by C in principle.
4.11. Cross-version compatibility
It may also be possible to extend this approach beyond
in order to reduce upgrade friction
.
For example, consider if we had a
keyword to deactivate label mangling:
Crucially, this enables shipping a header with the signature
in C++29 and
in any older version, down to C++98.
Without any ODR violation,
it would be possible to define the function in a source file later like:
This means that conditional compilation is only ever needed in headers, not in source files.
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.