back
83 comments
The title is slightly click-bait-y: C++20 doesn't have default parameters. It talks about how to fake it with structs.

It's a pretty convincing fake, but it has one problem: the person who wrote the function needs to have written it that way. What I wish more languages had (C++, Java, etc.) is the following:

Suppose I write some function that take way too many parameters:

void foo(char a, short b, long c, float d, double e){}

What I want, as a caller, is for the language to let me get a struct corresponding to the argument list, something like this:

    foo::args whatever;
    whatever.e = 3.14;
    // fill in the rest
And then add a bit of magic syntax so we can "unpack" that struct to actually call foo. (Extending this proposal to handle varargs is left as an exercise to the reader, because I have no idea.)
That's quite interesting.

I see two possible implementations:

- either the struct is replicated at the function call (not really efficient, but the semantics are straightforward);

- either the struct is used to prepare directly the stack for the function call (more efficient, but the semantics must be defined).

One of the issues of the second approach is that if the structure/variable is a classical one, what should happen if we use the variable after the call?

If we have to read the values inserted before the call then we have to keep a copy of the structure in the stack (so it's the same as the first approach). Or we can consider that the variable no longer exists in the scope. Which corresponds to the ownership concept in Rust for example. (but, to my knowledge, this does not exist in C++)

What is the best option? Or are there other options?

Another question, what sizeof(foo::args) should return ?

I think foo::args should be a normal struct type, so sizeof(foo::args) would follow the usual rules, just as if someone had manually written

struct foo_args{char a; short b; long c; float d; etc...}

and then asked for sizeof(foo_args).

How about `std::make_from_tuple` for constructors or `std::apply` for functions (C++17)?

It might be a bit verbose, but I think I would still prefer this over native syntax for unpacking.

Why would you kill your compile times like that.
This mostly solves the unpacking problem. Thus, there are only two problems left:

1. I need to handcraft the tuple type myself, and make sure it matches the function declaration. (In other words, I don't want to manually write tuple<char, short>. I want to write foo::arg_tuple.)

2. This provides no support for named parameters. Tuples in C++ are numbered, not named.

Though limited, C++ does support default parameters: https://en.cppreference.com/w/cpp/language/default_arguments.
sorry, I meant named parameters
Ahh this is very, very cool.

In general, it'd be really nice to have compile-time access to compile-time function info like this. Arguments, their types, etc.

This is commonly called `function_traits`--for example: https://www.boost.org/doc/libs/1_43_0/libs/type_traits/doc/h...
Having programmed in languages with named parameters (Smalltalk, ObjC and Commonlisp) I’ve found them cumbersome.

Also they can lead to a poor pattern which is functions/methods that take too many parameters.

They also tend to make generics harder to understand as the possible parameters can vary. Of course you can make the same problem in C++ via, say, `auto multiply(int x, int y)` and `auto multiply(double x, double y, enum rounding rp = rounding::nearest)`

In general they impose the verbosity (in keyboarding and reading) in the routine cases but don’t free you from having to look up the argument semantics of lesser-used functions.

Side point: I used “keyboarding” because the obvious word, “typing” has a homonym which made the sentence confusing!

Hmm. Why do you think they would lead to misuse via functions that take too many parameters?

I understand how this is a problem but I’ve seen more than one function in C that takes 4 or more parameters.

Methinks it’s more a product of being an inexperienced coder and not being able to model a domain appropriately. The huge benefit? If you do need 4 parameters you can at least read them / understand them if they are named!

Would love to see you flesh this out a bit more

Because most people can figure out that:

  CreateProcess(NULL,cmd,NULL,NULL,TRUE,CREATE_NO_WINDOW,NULL,NULL,&si,&pi);
is a terrible interface that we should try to split up better, but

  CreateProcess(
    lpApplicationName: NULL,
    lpCommandLine: cmd,
    lpProcessAttributes: NULL,
    lpThreadAttributes: NULL,
    bInheritHandles: TRUE,
    dwCreationFlags: CREATE_NO_WINDOW,
    lpEnvironment: NULL,
    lpCurrentDirectory: NULL,
    lpStartupInfo: &si,
    lpProcessInfo: &pi,
    );
tends not to attract the same scrutiny for some reason. On the other hand, removing named parameters doesn't actually fix this (see jasode's example at https://news.ycombinator.com/item?id=24401913 so I don't need another dozen lines), so it's not clear that this is a good argument against named parameters.
Well, my example was a small case of this problem. It’s not unreasonable that when operating on floating point, different algorithms can need different ways of determining which FP number to choose when the result cannot be precisely represented. And the example I gave is innocuous...though it’s likely an underlying function called by functions that implement higher level semantics.

And in fact in that case using a keyword isn’t that different from binding the meaning into the function name (`divide_rounding_down` and so on).

But in my experience I saw a pernicious pattern: that the function name would essentially become the common entry point for a large number of divergent functions `divide(dividend: x, divisir: y, truncate: true, underflow_handling: ufh::throw, negative_permitted: true)` and so on (contrived example for explanatory purposes).

In addition, positional arguments are simply easier to read; as keyword arguments are not ordered, you’re essentially parsing the arguments, seeking the important ones etc. You’re increasing the cognitive overhead of the common case while only marginally assisting the uncommon case. It’s like reading while hearing the sound of the words in your head: quite possible, but significantly slower than having the meaning enter directly.

Kotlin has a nice convention with named and variable arguments. If the types are all different, it's not ambiguous and the compiler can figure it out, but you're free to name them at the call site anyway. I really don't see how we've suffered the C convention for 40 years when we could have had this instead.

https://kotlinlang.org/docs/reference/functions.html

One thing to be aware of is that named arguments will become part of the public interface which you can't change without breaking everyone's code. I believe swift solves this by having internal and external names for arguments.
Python has a similar convention with args/kwargs, and I find it massively helpful when trying to become situated in a codebase I didn't write myself.
I don't know why named parameters haven't been added to C over the last 40 years. We had named struct initializers for 20 years.
That's what C# has been doing since v4 (2010).
What named parameters fixes is making constant parameters readable. You can API design around this (see for example https://wiki.qt.io/API_Design_Principles#The_Boolean_Paramet... ). You can also always use local constants.

But those are non-trivial or cumbersome in their own way, and often people don't for their own internal functions. Named parameters neatly side-steps the issue entirely, by rolling the "local constant" right into the function call itself.

Like consider these two python snippets:

     ['Ford', 'BMW', 'Volvo'].sort(true)
vs.

      ['Ford', 'BMW', 'Volvo'].sort(reverse=true)

The latter is much more readable than the former, and there wasn't any "too many parameters" issue. It's a way to help create self-documenting code that's also compiler enforced. And it absolutely frees the reader up from needing to go lookup what the boolean parameter on "sort" means.
Although I agree in general, I think that most of the time this should be solved on the API level (like your link suggests). This is because although you 'can' name your parameters, there is nothing forcing you to. This means that many people forget or are too lazy, so you still end up with a large part of your code not being as readable as it could be.

For the above you'd have an 'rsort' or 'reversesort' function instead (or if your language supports it, some fluent kind of sort().reverse()). If you have a lot of options, some of them boolean, you might send in an options struct or have an enum as mentioned in the article.

On the Windows team at Microsoft where I work, we work around the lack of actual named parameters by putting parameter names in C-style comments. To illustrate, given a hypothetical list class with a sort method like Python's, with a boolean "reverse" parameter, we'd call that method like this:

    list.sort(true /* reverse */);
This convention is very handy for certain Win32 functions that take lots ofparameters, such as CreateProcess or CreateWindowEx.
This would better be solved with keywords / literal types / singelton types

  ['Ford', 'BWM', 'Volvo'].sort(`reverse)
TypeScript can do that with strings: https://www.typescriptlang.org/docs/handbook/literal-types.h...
On no! Something in C++ that can be misused ;)
I found it quite useful & more natural to read in Objective-C/Swift & in those you typically are writing in an IDE that auto-completes all the names. I've also not generally found them cumbersome in Python where I code primarily in VIM without auto-completion. YMMV
I use the equivalent of named parameters in Ruby as my preferred method for a few reasons:

1. Clearly communicates the purpose of the variable

2. Means that order of parameters is not important

3. Makes the internal interface largely compatible with its JSON web service interface

It's been about 10 years since I coded any Objective-C and I do agree that syntax was an oddbal.

>Also they can lead to a poor pattern which is functions/methods that take too many parameters.

Never seen that as a tendency in languages with named params.

I have, however, seen functions / methods in languages without named params that take too many arguments and one has to guess what they are...

It's a feature that I learned to use in Python and one that I consistently now miss in other languages (rust, java, typescript, javascript).
In ts/js passing an object is not very much effort and doesn't add much noise to reading the code.
It's annoying in TS because (AFAIK?) you need to specify the type and unpack the object:

  function foo({arg1, arg2, arg3}:{arg1: Thing1, arg2: Thing2, arg3: Thing3) {
    ...
  }
Honestly, In my experience named params are better than positional ones, but then again, they’re not mutually exclusive.
Unfortunately in many cases this will cause all of the parameters to be passed via the stack rather than in registers. It’s a meaningful penalty for the kind of people who would willingly select c++ as a language today (rather than, say, python or java).
From my experience with C99: if the struct size is 16 bytes or less, the values will be packed into registers. But true as soon as the struct grows bigger, the entire content will be passed on the stack.

IMHO this sort of named arguments "easter egg" is fine for big "option bag structs" passed to functions that are not performance critical (or for small struct <= 16 bytes). For other cases, regular args are better.

OTH it seems like passing normal args also spills over into the stack very soon (only 4 registers used?):

https://www.godbolt.org/z/Mz9MTG

The main difference seems to be that normals arguments "spill over", while passing structs by value puts everything on the stack once the threshold size is passed.

In c++ it also seems likely that with this approach you could invoke some pretty expensive copy constructors if some of the parameters have those.
Per the sysv abi, sec. 3.2.3, parameter passing:

> If the size of an object is larger than eight eightbytes, or it contains unaligned fields, it has class MEMORY

> [...]

> If the size of the aggregate exceeds a single eightbyte, each is classified separately. Each eightbyte gets initialized to class NO_CLASS.

So that only happens if the whole structure is bigger than 64 bytes. Otherwise everything gets passed in registers like normal.

You can write a trivial template that generates a parameter pack which the compiler should be able to optimize at compile time.

However in my experience there are human factors problems which I posted in another comment.

Wouldn't be possible for the compiler to optimize it away?
Why?
This is my favorite feature of Objective-C / Smalltalk.

It makes everything so readable. I can come back to my code a few weeks later and I don’t really need to look up function signatures.

In particular, my favorite thing about this in Objective-C is that the C functions have a different way of being called so you can easily distinguish between the differences in paradigms.

I wonder what other languages force all calls to be via named parameters. Would love to have another option :)

Swift, kinda: named is the default but you can make parameters explicitly positional (by setting the label to _).

I guess in Python you could l’ont your codebase to require keyword parameters or opt-in positionals.

This is quite interesting, a very elegant struct hack. Dlang is working on an RFC for implementing this feature right into the language, which would probably make it the first systems language to implement this without structs.

https://github.com/dlang/DIPs/blob/master/DIPs/DIP1030.md

Ada arguably is a system programming language and it supports named parameters.
personally i’d just write a linter that adds names via comment

open(/port/ 127)

Personally I'd like c++ to:

a) have default parameters in any parameter position. I.e. for this to be legal:

    foo(int x = 0, int y, int z = 0) {}
b) to allow me to invoke a function like this:

    foo(,) //x and z are passed as default values:
c) or like this using named parameters:

    foo(x = 5, y = 1,)
Is there a way to have initialization fail at compile time if all members aren't included? So that if new members are added, all call sites become invalid until updated?
I don't think you'd want to. One of the primary usages of this kind of thing would be like when you need to pass in some massive struct of options to some dumb API function (you know the ones!), and then you'd absolutely want default values for all the things you don't want to specify.

Also, some might argue that what you're describing (call sites not becoming invalid when you update the function) is a feature, not a bug. This way, you can add options to your functions and still have everything compile like it should.

I wouldn't personally argue that though, i think it leads to bad practice where functions take a bazillion options and they become horrible tech debt. I'm working in a mixed C++ and Lua codebase, and the Lua codebase is littered with functions that take, like, 9 different arguments that change the behavior of the function. When I asked one of my colleagues about this style, he said it was one of his favorite features of Lua, that you can add arguments to a function to change the behavior in certain cases but keep the original functionality (guarded with default values or if's or whatever). It's made much of the code a living nightmare!

So, I guess, in the process of writing this comment I've come over to your side :) say no to adding arguments with default values!

You could probably define the arguments struct so that all its members have types without default constructors / with deleted default constructors.
Bravo, it's time