default arguments

331 views
Skip to first unread message

Arthur Tchaikovsky

unread,
Aug 15, 2015, 12:49:50 PM8/15/15
to ISO C++ Standard - Future Proposals
Hi,
Does anyone see helpfulness of such solution:
Having fnc:

void f(int a = 0, int b = 1, int c = 2, int d = 3);

I think that it would be nice to be able to say whilst calling it and requiring only some of those args to be non-default:

f(default,2,default,4);

Thoughts?





Nicol Bolas

unread,
Aug 15, 2015, 1:29:30 PM8/15/15
to ISO C++ Standard - Future Proposals

This is probably the best language enhancement to default arguments I've seen (admittedly, that's not saying much, considering the quality of the others). It uses an existing keyword in a way that doesn't break existing code. It provides genuinely useful functionality, solving a (relatively minor) problem. And it would make it reasonable to define default arguments other than at the end of a parameter list.

My only concern is that it could tie up more useful syntax. With Uniform Initialization syntax, you can perform value initialization and construction initialization. But there's no way to generically use default initialization. So we might someday want `f(default)` to mean "default initialize the parameter".

Then again, maybe we don't want that.

David Krauss

unread,
Aug 15, 2015, 8:19:56 PM8/15/15
to std-pr...@isocpp.org

On 2015–08–16, at 1:29 AM, Nicol Bolas <jmck...@gmail.com> wrote:

My only concern is that it could tie up more useful syntax. With Uniform Initialization syntax, you can perform value initialization and construction initialization. But there's no way to generically use default initialization. So we might someday want `f(default)` to mean "default initialize the parameter".

C-style variadic arguments come close to implementing default-initialized (uninitialized) parameters. The receiver needs to explicitly make a decision whether to access the parameter and assert that it was actually passed.

Default-initialized values, when distinct from value-initialized, are indeterminate. Parameter passing uses copy-initialization, and you usually can’t copy an indeterminate value. So f(default) would seem to be UB except when the argument is a temporary passed by reference.

TL;DR: Default-initialized parameters, not very useful.

Arthur Tchaikovsky

unread,
Aug 17, 2015, 2:37:26 PM8/17/15
to ISO C++ Standard - Future Proposals
I would really appreciate some feedback from people, or is this proposal so uninteresting that there is no point in wasting keyboard on it?

dgutson .

unread,
Aug 17, 2015, 2:56:35 PM8/17/15
to std-proposals

I think you should google better ;-)

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2003/n1466.pdf

>
>
>
>
>
> --
>
> ---
> 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-proposal...@isocpp.org.
> To post to this group, send email to std-pr...@isocpp.org.
> Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.

Bo Persson

unread,
Aug 17, 2015, 2:58:41 PM8/17/15
to std-pr...@isocpp.org
On 2015-08-17 20:37, Arthur Tchaikovsky wrote:
> I would really appreciate some feedback from people, or is this proposal
> so uninteresting that there is no point in wasting keyboard on it?

You might wonder how widely used it will be?

If not all parameters are of the same type, you can already get some of
the effect with a few overloads:

void f(int a = 0, string b = "1",
complex<double> c = 2.0, type4 d = 3);

inline void f(int a, complex<double> c)
{ f(a, "1", c, 3); }

inline void f(string b, complex<double> c)
{ f(0, b, c, 3); }

assuming that d is put last because you hardly ever want to change the
default.

Just saying that you can already do this without a language change.


Bo Persson

Arthur O'Dwyer

unread,
Aug 17, 2015, 6:52:24 PM8/17/15
to ISO C++ Standard - Future Proposals, b...@gmb.dk
I work on a codebase where this feature would have lots of use-cases, because we have lots of classes whose constructors have defaulted parameters:

    C(int buffer_size = 1024, int timeout = 10, int tries = 3) : buffer_size_(buffer_size), timeout_(timeout), tries_(tries) {}

    auto cptr = new C();  // usually the defaults are okay
    auto c2ptr = new C(2048);  // use a bigger buffer
    auto c3ptr = new C(1024, 1);  // use the default buffer size but with a shorter timeout
    auto c4ptr = new C(2048, 10, 5);  // testing: bump up the number of tries

It would be marginally more convenient (and refactor-proof) to be able to write

    auto cptr = new C();  // usually the defaults are okay
    auto c2ptr = new C(2048);  // use a bigger buffer
    auto c3ptr = new C(default, 1);  // use the default buffer size but with a shorter timeout
    auto c4ptr = new C(2048, default, 5);  // testing: bump up the number of tries

However, I consider defaulted parameters in general to be a huge antipattern, and this constructor pattern in particular to be even worse.
I would MUCH rather just rewrite all these constructors so that they could be used as

    auto cptr = new C();  // usually the defaults are okay
    auto c2ptr = new C().withBufferSize(2048);  // use a bigger buffer
    auto c3ptr = new C().withTimeout(1);  // use the default buffer size but with a shorter timeout
    auto c4ptr = new C().withBufferSize(2048).withTries(5);  // testing: bump up the number of tries

or

    auto cptr = new C();  // usually the defaults are okay
    auto c2ptr = new C(C::BufferSize(2048));  // use a bigger buffer
    auto c3ptr = new C(C::Timeout(1));  // use the default buffer size but with a shorter timeout
    auto c4ptr = new C(C::BufferSize(2048), C::Tries(5));  // testing: bump up the number of tries

or (the most conservative way to rewrite our existing code)

    static constexpr int default_buffer_size = 1024;
    static constexpr int default_timeout = 10;
    static constexpr int default_tries = 3;
    C() : buffer_size_(default_buffer_size), timeout_(default_timeout), tries_(default_tries) {}
    C(int buffer_size, int timeout, int tries) : buffer_size_(buffer_size), timeout_(timeout), tries_(tries) {}

    auto cptr = new C();  // usually the defaults are okay
    auto c2ptr = new C(2048, C::default_timeout, C::default_tries);  // use a bigger buffer
    auto c3ptr = new C(C::default_buffer_size, 1, C::default_tries);  // use the default buffer size but with a shorter timeout
    auto c4ptr = new C(2048, C::default_timeout, 5);  // testing: bump up the number of tries

I don't really want to make it easier for people to write the "function taking tons of defaulted parameters" antipattern.
Let's teach people how to avoid the use of defaulted parameters, instead.

–Arthur

Roland Bock

unread,
Aug 18, 2015, 1:12:38 AM8/18/15
to std-pr...@isocpp.org
On 2015-08-18 00:52, Arthur O'Dwyer wrote:
I work on a codebase where this feature would have lots of use-cases, because we have lots of classes whose constructors have defaulted parameters:

    C(int buffer_size = 1024, int timeout = 10, int tries = 3) : buffer_size_(buffer_size), timeout_(timeout), tries_(tries) {}

    auto cptr = new C();  // usually the defaults are okay
    auto c2ptr = new C(2048);  // use a bigger buffer
    auto c3ptr = new C(1024, 1);  // use the default buffer size but with a shorter timeout
    auto c4ptr = new C(2048, 10, 5);  // testing: bump up the number of tries

It would be marginally more convenient (and refactor-proof) to be able to write

    auto cptr = new C();  // usually the defaults are okay
    auto c2ptr = new C(2048);  // use a bigger buffer
    auto c3ptr = new C(default, 1);  // use the default buffer size but with a shorter timeout
    auto c4ptr = new C(2048, default, 5);  // testing: bump up the number of tries

However, I consider defaulted parameters in general to be a huge antipattern, and this constructor pattern in particular to be even worse.

+10 (at least)


I would MUCH rather just rewrite all these constructors so that they could be used as

    auto cptr = new C();  // usually the defaults are okay
    auto c2ptr = new C().withBufferSize(2048);  // use a bigger buffer
    auto c3ptr = new C().withTimeout(1);  // use the default buffer size but with a shorter timeout
    auto c4ptr = new C().withBufferSize(2048).withTries(5);  // testing: bump up the number of tries

or

    auto cptr = new C();  // usually the defaults are okay
    auto c2ptr = new C(C::BufferSize(2048));  // use a bigger buffer
    auto c3ptr = new C(C::Timeout(1));  // use the default buffer size but with a shorter timeout
    auto c4ptr = new C(C::BufferSize(2048), C::Tries(5));  // testing: bump up the number of tries

Or (if you are definining individual types for your parameters anyway), why not

    auto cptr = new C();  // usually the defaults are okay
    auto c2ptr = new C(bufferSize = 2048);  // use a bigger buffer
    auto c3ptr = new C(timeout = 1);  // use the default buffer size but with a shorter timeout
    auto c4ptr = new C(bufferSize = 2048, tries = 5);  // testing: bump up the number of tries

You would have but one constructor (assuming Concepts Lite and Folding Expressions):

template<Assignment... A)
C(const A&... a) : buffer_size_(1024), timeout_(10), tries_(3)
{
  (a(*this),...);
}

And some types/constexprs:

struct BufferSizeAssignment
{
   int value;

   template<typename T>
   void operator()(T& t)
   {
      t.buffer_size_ = value; // or t.setBufferSize(value)
   }
};

struct BufferSize
{
   BufferSizeAssignment operator=(int value)
   {
      return {value};
   }
};

constexpr auto bufferSize = BufferSize{};


Of course, this would be much more convenient if we could configure names in template parameters and could write

constexpr auto bufferSize = std::named_parameter<int, `buffer_size_`>;
constexpr auto timeout = std::named_parameter<int, `timeout_`>;
constexpr auto tries = std::named_parameter<int, `tries_`>;

Still trying to find the time to write the proposal for that...


Best,

Roland

Arthur Tchaikovsky

unread,
Aug 18, 2015, 5:20:20 AM8/18/15
to ISO C++ Standard - Future Proposals, b...@gmb.dk
"I would MUCH rather just rewrite all these constructors"

But did you do it or simply wish that at some point you will find the time (and will) to do it, but in the mean time you are still using those "old" constructors? Because I also would MUCH rather have C++ cleaned up, yet this will never happen, and I need to use C++ in its current state. What I wish is very different to what I have.

And as for you saying that defaulted constructors are antipattern, is that a fact or it is simply your opinion?

Arthur Tchaikovsky

unread,
Aug 18, 2015, 5:26:23 AM8/18/15
to ISO C++ Standard - Future Proposals
@dgutson
I've typed the phrase in google, nothing came up on first page.
So to your reply I say:
I think you should be more polite, and instead of say what you've just said, you should say:
"Hi, I've already proposed something identical really, great to have someone who thinks along the same lines ;)"

But no, instead of being civilized and pleasant you've decided to post smart as** comment. And you consider yourself a professional? Unbelievable.

Germán Diago

unread,
Aug 18, 2015, 5:27:03 AM8/18/15
to ISO C++ Standard - Future Proposals

f(default,2,default,4);

I think that named arguments built into the language would solve the same problem and in a more general way:

f(b : 2, d : 4);

I recall there was a paper about it somewhere at some point. I do not know if it is the last one though:



 

Johannes Schaub

unread,
Aug 18, 2015, 5:34:39 AM8/18/15
to std-pr...@isocpp.org

I would also wanna solve this with named arguments

  f(.b = 2, .d = 4)

And index based

  f([1] = 2, [3] = 4)

Johannes Schaub

unread,
Aug 18, 2015, 5:34:39 AM8/18/15
to std-pr...@isocpp.org


Am 15.08.2015 18:49 schrieb "Arthur Tchaikovsky" <atch...@gmail.com>:
>

And if you support the default indicator here, please allow it in aggregate initialization aswell to mean either value initialize or take the inclass initializer.

dgutson .

unread,
Aug 18, 2015, 5:35:29 AM8/18/15
to std-proposals


El 18/8/2015 6:26, "Arthur Tchaikovsky" <atch...@gmail.com> escribió:
>
> @dgutson
> I've typed the phrase in google, nothing came up on first page.
> So to your reply I say:
> I think you should be more polite, and instead of say what you've just said, you should say:
> "Hi, I've already proposed something identical really, great to have someone who thinks along the same lines ;)"
>
> But no, instead of being civilized and pleasant you've decided to post smart as** comment. And you consider yourself a professional? Unbelievable.

I'm just someone with different sense of humor and who wrote something that also considered overloading more than 10 years ago. You might wonder now why the proposal didn't suceed then, in case you become unoffended.

Ville Voutilainen

unread,
Aug 18, 2015, 5:36:00 AM8/18/15
to ISO C++ Standard - Future Proposals
That paper was rejected: http://cplusplus.github.io/EWG/ewg-closed.html#150
Every other attempt at it has been rejected as well.

Arthur Tchaikovsky

unread,
Aug 18, 2015, 5:40:45 AM8/18/15
to ISO C++ Standard - Future Proposals
Fair play, bit of a friendly fire I suppose. ;)

Arthur Tchaikovsky

unread,
Aug 18, 2015, 5:42:19 AM8/18/15
to ISO C++ Standard - Future Proposals
That genuinely seems pointless/silly to me, unless you can provide some convincing use cases.

Johannes Schaub

unread,
Aug 18, 2015, 5:46:19 AM8/18/15
to std-pr...@isocpp.org


Am 18.08.2015 11:42 schrieb "Arthur Tchaikovsky" <atch...@gmail.com>:
>
> That genuinely seems pointless/silly to me, unless you can provide some convincing use cases.
>

These are designated initializers and why stop at making initialization uniform just before designated initializers?

It seems natural to me to allow them in function argument lists including for constructors. And the index notion looks like an elegant solution to skip arguments.

Arthur Tchaikovsky

unread,
Aug 18, 2015, 6:22:15 AM8/18/15
to ISO C++ Standard - Future Proposals
I gotta be honest with you Johann, I simply don't buy it.
And index notion looks anything but elegant solution. That is my opinion of course.

David Rodríguez Ibeas

unread,
Aug 18, 2015, 6:22:29 AM8/18/15
to std-pr...@isocpp.org
On Tue, Aug 18, 2015 at 10:42 AM, Arthur Tchaikovsky <atch...@gmail.com> wrote:
That genuinely seems pointless/silly to me, unless you can provide some convincing use cases.

On Tuesday, 18 August 2015 10:34:39 UTC+1, Johannes Schaub wrote:

I would also wanna solve this with named arguments

  f(.b = 2, .d = 4)

And index based

  f([1] = 2, [3] = 4)



Any use case in which you may end up using 'default' in your proposal is a use case for these two approaches.  In the first case, it lets you focus on what parameters are being configured (rather than defaulted) without having to look at the function declaration, 'f(.b = 2, .d = 4)' is equivalent to 'f(default, 2, default, 4)' and I find the former easier to read.

Using indices is something that I had not considered before in the context of a regular function call, but I can imagine how 'f([3] = 4)' might be more readable than 'f(default, default, default, 4)' on two accounts: more concise and the counting is done for you.

Now, going back from the high level desire to the implementability and potential issues, these two are allowed as initializers for structs in C (which is probably where Johannes is comming from), and while the former ('f(.b = 2)') is kind of clean the latter does lead to surprises for inexperienced programmers:

int a[10] = { [3] = 1, [2] = 5, 6 }; // what values are in the array?

Still, that might be easier than implementing named arguments.  The "named" arguments work nicely with a 'struct' since the type can be defined only once, but it leads to different sorts of confusion for functions that may have multiple declarations:

void f(int a = 1, int b = 2);
void f(int b = 1, int a = 2);  // redeclaration

f( .a = 5 ); // f(5, 2)? f(1, 5)? 

Which AFAIK is one of the issues that this form of proposals have faced in the past.  I recall also some discussions in this forum (or maybe even papers?) aiming for a syntax that would allow this by enabling warning/errors when a redeclaration used different names than the original declaration but I did not see any of those prosper. BTW, forcing the same name in redeclarations is part of what our internal coding guidelines mandate and we have tools for verification, not sure about other shops.

Personally I have shifted from liking default arguments to trying to avoid them if at all possible as they complicate changes in old code (add a new non-defaulted argument and if unlucky users passing all arguments explicitly will not fail to compile but do something different than they intended).

Overall, I find that if a nice clean solution could be reached, it would benefit the language, on those lines, I'd rather go into solutions that allow tagging a function as "names are important" (maybe a function attribute), having the compiler enforce that redeclarations use the same names for functions tagged as such [optionally allow a name to be dropped, but not changed, for implementations that might not use some of the arguments] and then using named arguments in the call seems cleaner than having to count the number of 'default'. On the other hand, this proposal is much easier to get into the language.

    David

David Rodríguez Ibeas

unread,
Aug 18, 2015, 6:25:13 AM8/18/15
to std-pr...@isocpp.org
On Tue, Aug 18, 2015 at 11:22 AM, Arthur Tchaikovsky <atch...@gmail.com> wrote:
I gotta be honest with you Johann, I simply don't buy it.
And index notion looks anything but elegant solution. That is my opinion of course.

 
You  might not find it elegant, but it is a feature that has been present in C for some time (to initialize objects) so there is prior art. It is also tested and the shortcomings are known --at least for initialization, which is not really the same as defaulting arguments to functions or constructors.

Andrey Semashev

unread,
Aug 18, 2015, 6:39:22 AM8/18/15
to std-pr...@isocpp.org
Structure members have stable names, function parameters do not and in
fact may have no names. I do find Boost.Parameter named parameters
useful sometimes, but with the existing rules defined by the standard
wrt function declarations I really doubt the named function arguments
will appear in the language as easily as you suggest. I would find
named member initializations for structs extremely useful though, as I
constantly find myself worrying that braced initialization will get
screwed when the order of the struct members changes.

About the original proposal (i.e. the use of the 'default' keyword to
designate default argument values), I like it. Not that I often need
something like that but sometimes that would be useful, at least to
decouple the interface from the user.

Arthur Tchaikovsky

unread,
Aug 18, 2015, 6:58:47 AM8/18/15
to ISO C++ Standard - Future Proposals, dib...@ieee.org
Yes, but this is C++ not C,  and we are trying to make C++ more modern not stick with the past, right?
Secondly, the idea that is designated initializers, seem to me that it is violation of encapsulation, so really, personally I am against it/don't like it.

Nicol Bolas

unread,
Aug 18, 2015, 9:54:39 AM8/18/15
to ISO C++ Standard - Future Proposals, b...@gmb.dk


On Tuesday, August 18, 2015 at 5:20:20 AM UTC-4, Arthur Tchaikovsky wrote:
"I would MUCH rather just rewrite all these constructors"

But did you do it or simply wish that at some point you will find the time (and will) to do it, but in the mean time you are still using those "old" constructors? Because I also would MUCH rather have C++ cleaned up, yet this will never happen, and I need to use C++ in its current state. What I wish is very different to what I have.

And as for you saying that defaulted constructors are antipattern, is that a fact or it is simply your opinion?

... What else would it be but an opinion? Is there some entirely objective definition of "anti-pattern" that could be applied? Of course not; its definition is based on user experience, which is always at least somewhat subjective.

That being said, if quite a few C++ programmers, particularly those who frequently work with codebases that use them, consider something to be an anti-pattern, that's at least evidence for the position. And I don't think I've seen too many people saying "we need people using default arguments in C++ more".

I'm not against the functionality on principle. It's a simpler solution than named arguments. And considering that the C++ committee has decided that named arguments pretty much are never going to happen, it's probably the best you can hope for.

At the same time, I don't think it's a very important feature. I agree with the idea that using lots of default arguments (which this feature encourages) is at the very least a code smell if not an anti-pattern. So I can't say that this is a good idea.

Matthew Woehlke

unread,
Aug 18, 2015, 10:03:38 AM8/18/15
to std-pr...@isocpp.org
On 2015-08-18 06:22, David Rodríguez Ibeas wrote:
> On Tue, Aug 18, 2015 at 10:42 AM, Arthur Tchaikovsky wrote:
>> On Tuesday, 18 August 2015 10:34:39 UTC+1, Johannes Schaub wrote:
>>> I would also wanna solve this with named arguments
>>>
>>> f(.b = 2, .d = 4)
>>
>> That genuinely seems pointless/silly to me, unless you can provide some
>> convincing use cases.
>
> Any use case in which you may end up using 'default' in your proposal is a
> use case for these two approaches. In the first case, it lets you focus on
> what parameters are being configured (rather than defaulted) without having
> to look at the function declaration, 'f(.b = 2, .d = 4)' is equivalent to
> 'f(default, 2, default, 4)' and I find the former easier to read.

It's also self-documenting. Consider Arthur O'Dwyer's examples; which is
easier to understand?

auto cptr = new C(); // usually the defaults are okay
auto c2ptr = new C(2048); // use a bigger buffer
auto c3ptr = new C(default, 1); // use a shorter timeout

- vs. -

auto cptr = new C{};
auto c2ptr = new C{.buffer = 2048};
auto c3ptr = new C{.timeout = 1};

There's a reason named arguments are so popular in Python :-).

> int a[10] = { [3] = 1, [2] = 5, 6 }; // what values are in the array?

This never happens because your example is ill-formed. Parameters that
are neither named nor indexed must precede parameters that are. (In
Python also, and for similar reasons.)

> Still, that might be easier than implementing named arguments. The "named"
> arguments work nicely with a 'struct' since the type can be defined only
> once, but it leads to different sorts of confusion for functions that may
> have multiple declarations:
>
> void f(int a = 1, int b = 2);
> void f(int b = 1, int a = 2); // redeclaration
>
> Which AFAIK is one of the issues that this form of proposals have faced in
> the past. I recall also some discussions in this forum (or maybe even
> papers?) aiming for a syntax that would allow this by enabling
> warning/errors when a redeclaration used different names than the original
> declaration but I did not see any of those prosper.

Probably there should be a way of naming parameters that "codifies" the
name, making it part of the ABI. And, yes, such redeclarations would be
disallowed.

--
Matthew

Arthur Tchaikovsky

unread,
Aug 18, 2015, 12:37:55 PM8/18/15
to ISO C++ Standard - Future Proposals, mwoehlk...@gmail.com
I dear to disagree with the statement that the following example is self documenting and easier to read:

auto c3ptr = new C{.timeout = 1};
How is this^^^ easier to read? And how is this self documenting? Only on surface it seems that it is, both, but when you really think about it it is neither self documenting nor easy to read. Why? Because having only one argument initialized in {}, that is .timeout, leaves us with question, is that the only argument to that class, or are there any other? If so, are those arguments inited by default? If so, how many? Etc.
Compared to this:

auto c3ptr = new C(default, 1); // use a shorter timeout
It is immediately obvious how many arguments there are and which one of them are default. And as for self documentation?
auto c3ptr = new C(default, /*timeout*/1); // use a shorter timeout

Andrey Semashev

unread,
Aug 18, 2015, 12:49:08 PM8/18/15
to std-pr...@isocpp.org, mwoehlk...@gmail.com
On Tue, Aug 18, 2015 at 7:37 PM, Arthur Tchaikovsky <atch...@gmail.com> wrote:
> I dear to disagree with the statement that the following example is self
> documenting and easier to read:
> auto c3ptr = new C{.timeout = 1};
> How is this^^^ easier to read? And how is this self documenting? Only on
> surface it seems that it is, both, but when you really think about it it is
> neither self documenting nor easy to read. Why? Because having only one
> argument initialized in {}, that is .timeout, leaves us with question, is
> that the only argument to that class, or are there any other? If so, are
> those arguments inited by default? If so, how many? Etc.
> Compared to this:
> auto c3ptr = new C(default, 1); // use a shorter timeout
> It is immediately obvious how many arguments there are and which one of them
> are default.

Your argument works both ways - I cannot tell what other members C has
and how they are initialized by this constructor. In addition I don't
see what the constructor arguments are without looking at the
interface or docs.

> And as for self documentation?
> auto c3ptr = new C(default, /*timeout*/1); // use a shorter timeout

Multi-line comments are evil - they cause problems when you want to
comment a block of code. Besides interface documentation I try to
avoid them as much as possible.

IMHO, the true named member initialization is much more
self-explanatory and less error prone.

Arthur Tchaikovsky

unread,
Aug 18, 2015, 1:01:00 PM8/18/15
to ISO C++ Standard - Future Proposals, mwoehlk...@gmail.com
I believe that the conclusion is that they both have pluses and minuses.

Nicol Bolas

unread,
Aug 18, 2015, 1:23:31 PM8/18/15
to ISO C++ Standard - Future Proposals, mwoehlk...@gmail.com


On Tuesday, August 18, 2015 at 12:37:55 PM UTC-4, Arthur Tchaikovsky wrote:
I dear to disagree with the statement that the following example is self documenting and easier to read:
auto c3ptr = new C{.timeout = 1};
How is this^^^ easier to read? And how is this self documenting? Only on surface it seems that it is, both, but when you really think about it it is neither self documenting nor easy to read. Why? Because having only one argument initialized in {}, that is .timeout, leaves us with question, is that the only argument to that class, or are there any other?

If I'm looking to understand what `new C{.timeout=1};` means, I don't care how many arguments `C` could have in its constructor. From the user's perspective, that's simply irrelevant. What matters is what it means to pass the value `1` to the `timeout` argument of the constructor.

And since the word `timeout` is there, I can at least take a pretty good guess as to what it means.

Remember: the whole point of default arguments is that you don't have to know about them. To make a function that takes 6 arguments look like a function that takes 3. Seeing `default` for an argument is just pointless noise, which is necessary only because C++ doesn't allow for positional or named arguments.
 
If so, are those arguments inited by default? If so, how many? Etc.
Compared to this:
auto c3ptr = new C(default, 1); // use a shorter timeout
It is immediately obvious how many arguments there are and which one of them are default. And as for self documentation?
auto c3ptr = new C(default, /*timeout*/1); // use a shorter timeout

Comments are not "self documentation". Comments are not checked; syntax is.

Johannes Schaub

unread,
Aug 18, 2015, 1:28:41 PM8/18/15
to std-pr...@isocpp.org
2015-08-18 18:37 GMT+02:00 Arthur Tchaikovsky <atch...@gmail.com>:
> I dear to disagree with the statement that the following example is self
> documenting and easier to read:
> auto c3ptr = new C{.timeout = 1};
> How is this^^^ easier to read? And how is this self documenting? Only on
> surface it seems that it is, both, but when you really think about it it is
> neither self documenting nor easy to read. Why? Because having only one
> argument initialized in {}, that is .timeout, leaves us with question, is
> that the only argument to that class, or are there any other? If so, are
> those arguments inited by default? If so, how many? Etc.
> Compared to this:
> auto c3ptr = new C(default, 1); // use a shorter timeout
> It is immediately obvious how many arguments there are and which one of them
> are default. And as for self documentation?

Err, no. There may be other parameters that have default arguments,
that you don't see aswell. So you don't see all parameter's
initializer values in both versions. So I fail to understand your
argument. I even fail earlier, without finding the counter example.
Why would you even care about the other arguments values? Do you care
about values that might influence the constructor's behavior that are
*not* passed as arguments but by another way, perhaps read as globals?

In summary, I think you are trying to find arguments against ".timeout
= ..." and come up with not-so-convincing examples.

Matthew Woehlke

unread,
Aug 18, 2015, 1:35:42 PM8/18/15
to std-pr...@isocpp.org
On 2015-08-18 12:37, Arthur Tchaikovsky wrote:
> I dear to disagree with the statement that the following example is self
> documenting and easier to read:
> auto c3ptr = new C{.timeout = 1};
> How is this^^^ easier to read? And how is this self documenting?

Others have addressed this thoroughly; no need for me to repeat their
answers.

> If [the ctor takes more arguments than are named], are those
> arguments inited by default?

Um... yes? Any optional parameter not specified is defaulted. As always.
Nothing has changed here.

--
Matthew

Matt Calabrese

unread,
Aug 18, 2015, 2:12:15 PM8/18/15
to ISO C++ Standard - Future Proposals
On Sat, Aug 15, 2015 at 9:49 AM, Arthur Tchaikovsky <atch...@gmail.com> wrote:
Hi,
Does anyone see helpfulness of such solution:
Having fnc:

void f(int a = 0, int b = 1, int c = 2, int d = 3);

I think that it would be nice to be able to say whilst calling it and requiring only some of those args to be non-default:

f(default,2,default,4);

Thoughts?

Yes, I think it's useful. Yes, I think named arguments are more useful, though while there is overlap, they don't exactly solve the same set of problems. Even with named arguments you might potentially want/need something like this when in the presence of overloads.

However, I think named arguments are considerably less likely to get into the language due to their complexity, but those are just my thoughts on the matter. Defaults as you present them are considerably simpler and I also would not consider it an "anti-pattern."

So, +1 on this or a similar proposal.

Max Truxa

unread,
Aug 19, 2015, 2:02:01 AM8/19/15
to ISO C++ Standard - Future Proposals
I have to agree with Matt Calabrese completely. As it was already asserted named arguments are not going to happen anytime soon (if at all).

Earlier in this thread there were workarounds listed which are more or less comparable to the proposal in their effect.
I would like to elaborate some more on one of these workarounds, as it can be mapped pretty much 1-to-1 to the proposal and it happens to be the solution I employ when encountering this kind of problem.


# Current workaround.

struct s {
static int const default_a = 1337;
static int const default_b = 4096;
s(int a = default_a, int b = default_b)
: a(a), b(b) { }
int a;
int b;
};

int main() {
s x;
s y(42);
s z(s::default_a, 42);
return 0;
}

See working code here: http://ideone.com/OKtbuc


# Using the proposed syntax.

struct s {
s(int a = 1337, int b = 4096)
: a(a), b(b) { }
int a;
int b;
};

int main() {
s x;
s y(42);
s z(default, 42);
return 0;
}

The current solution has a number of drawbacks.
1) It's unnecessarily verbose.
2) It's easy to mess up for parameters that have the same type. (`s z(s::default_b, 42)` - Oops, passed `default_b` as `a`.)
3) When looking up the actual default values there is one more hop to make. It's not sufficient to look at the function declaration but additionally we have to look for e.g. default_a.

S.B.

unread,
Aug 19, 2015, 11:08:52 AM8/19/15
to ISO C++ Standard - Future Proposals
It's even simpler if we could write

struct s {
   
int a = 1337;
   
int b = 4096;
};
 
int main() {
    s x
;
    s y
{ 42 }; // or s y { .a = 42 };
    s z
{ .b = 42 };
   
return 0;
}

:)

Arthur Tchaikovsky

unread,
Aug 19, 2015, 12:27:09 PM8/19/15
to ISO C++ Standard - Future Proposals
It *is not* simpler. What you've just shown is a violation of encapsulation. Not simplification.

David Rodríguez Ibeas

unread,
Aug 19, 2015, 1:50:13 PM8/19/15
to std-pr...@isocpp.org
How does that violate an encapsulation that does not exist? All members are public in the struct… you might dislike it on different accounts but encapsulation is not an issue with that code.  Other pro´s, the arguments are named, which is stable across refactors that might shift the positions (assuming sensible names, the caller wants the 'a' to have value X or the 'b' to have value Z. Depending on the position of the member in the struct (and passing 'default' if needed for the previous arguments) is *more* coupled, not less coupled.

For initialization of structs, the C way is clearly better than the C++ way here. A different question is whether we want to put some effort into that part of the language when we can assume that most types are not all-members-public. 

Then again this is only related to default arguments to functions in that uniform initialization would be even less uniform in certain cases.  I do see the problems with named arguments, I still think that to be the better way. Whether initialization of structs can be made compatible with C once again… would be nice here, but I imagine not that many people care.

     David

--

Arthur Tchaikovsky

unread,
Aug 19, 2015, 2:07:00 PM8/19/15
to ISO C++ Standard - Future Proposals, dib...@ieee.org
Not all members in struct are public. From elementary school, everyone knows that struct differs from class that *by default* members are public in struct and private in class. ;)
That's why I see it as a violation of encapsulation. Secondly, not only structs should be concerned, but classes too. And not only initialization should be a concern here but also setting value *after* the object had been initialized. Taken all this above, I say that accessing members of a class/struct by names, be it during initialization or after that fact is violation of encapsulation and it should be discouraged in modern C++. This kind of code may very well be OK for C but C++ is not C.

Brent Friedman

unread,
Aug 19, 2015, 2:13:56 PM8/19/15
to std-pr...@isocpp.org
Arthur, please refrain from implying that someone needs to go back to elementary school based on your personal interpretation of something that someone else said. Also, if you know of an elementary school that teaches C++ then I'd love to hear about it.

The notion that every single class ever needs to have strict encapsulation for every single data member is fairly ridiculous in my opinion. Encapsulation is a tool used to achieve certain software engineering goals and it can be misused like any other tool.

Andrey Semashev

unread,
Aug 19, 2015, 2:39:10 PM8/19/15
to std-pr...@isocpp.org
On 19.08.2015 21:07, Arthur Tchaikovsky wrote:
> Not all members in struct are public. From elementary school, everyone
> knows that struct differs from class that *by default* members are
> public in struct and private in class. ;)
> That's why I see it as a violation of encapsulation.

I don't see the connection between the default access mode and
encapsulation. Encapsulation is implementation hiding. It can be
achieved by different means, including but not limited to member access
management. Classes are no different from structs except for the default
access mode - both can be used to achieve the same level of encapsulation.

And no, all-public structs do not violate encapsulation in general. For
instance, std::pair - a struct with public data, does not break
encapsulation of std::(unordered_)map.

> Secondly, not only
> structs should be concerned, but classes too. And not only
> initialization should be a concern here but also setting value *after*
> the object had been initialized. Taken all this above, I say that
> accessing members of a class/struct by names, be it during
> initialization or after that fact is violation of encapsulation and it
> should be discouraged in modern C++. This kind of code may very well be
> OK for C but C++ is not C.

I don't mean to sound harsh, but that's narrow minded thinking. The 'OOP
everywhere' era has passed as people realized that it is not efficient
in all scenarios. C-style structs are useful in some contexts and named
member initialization is useful as well. See ffmpeg code for instance,
the way codecs and formats are described. That's C, naturally, but
imagine how much more code that would require in today's C++ with
private members, constructors and default arguments. And then imagine
how difficult it would be to modify the struct used as the descriptor.

Arthur O'Dwyer

unread,
Aug 19, 2015, 7:53:13 PM8/19/15
to std-pr...@isocpp.org
On the subject of C and named parameters, I'll add that Ben Klemens' "21st Century C" shows a very nice idiom for getting Python-style named parameters out of a C99 compiler. I'm paraphrasing from memory a bit in the code sample below...

struct MyFunc_parameters {
  int buffer_size;
  int timeout;
  int tries;
};
#define MyFunc(...) MyFunc_((struct MyFunc_parameters){ .buffer_size=1024, .timeout=5, .tries=3, ##__VA_ARGS__ })
void MyFunc_(struct MyFunc_parameters p) {
  for (int i=0; i < p.tries; ++i) {
    allocate(p.buffer_size);
    if (time() > p.timeout) bail();
  }
}

int main() {
  MyFunc();
  MyFunc(.buffer_size = 2048);
  MyFunc(.timeout = 10);
  MyFunc(.tries = 5, .buffer_size = 2048);
}

(I don't think the book is suggesting that every function in every C program ought to use this idiom, any more than every Python function ought to use **kwargs. It's just a nice technique for building user-friendly C99 APIs.)

This uses a whole bunch of C99-specific features; it's unlikely that C++ will ever get all of the necessary features. But it shows that you don't need specific language-level support for defaulted parameters in order to achieve Pythonesque syntax — and in fact a syntax nicer than C++98's defaulted parameters! (Because the caller doesn't need to care about the order in which the callee declared its arguments; and because it's obvious to the callee that the "parameters"' names are part of the API.)

–Arthur



--

---
You received this message because you are subscribed to a topic in the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this topic, visit https://groups.google.com/a/isocpp.org/d/topic/std-proposals/3iegpTsMuT0/unsubscribe.
To unsubscribe from this group and all its topics, send an email to std-proposal...@isocpp.org.

David Krauss

unread,
Aug 19, 2015, 9:59:08 PM8/19/15
to std-pr...@isocpp.org

On 2015–08–20, at 7:53 AM, Arthur O'Dwyer <arthur....@gmail.com> wrote:

On the subject of C and named parameters, I'll add that Ben Klemens' "21st Century C" shows a very nice idiom for getting Python-style named parameters out of a C99 compiler. I'm paraphrasing from memory a bit in the code sample below…

Perhaps the syntax of an expression-list beginning with “.” should indicate implicit braces, added as sugar. I think that passing a struct is a superior design when lots of parameters are present, and one supported by decades of experience.

struct parameters {
  int buffer_size = 1024;
  int timeout = 10;
  int tries = 3;
};

int foo( parameters );
int x = foo( .buffer_size = 100, .timeout = 2, .tries = 3 ); // implicit braces

class non_aggregate {
public:
  non_aggregate( int, int, int ); // A
  non_aggregate( parameters ); // B
};

non_aggregate a( 1, 2, 3 ); // A
non_aggregate b{ 1, 2, 3 }; // A
non_aggregate c({ .buffer_size = 100, .timeout = 2, .tries = 3 }); // B (no extension beyond designated initializers)
non_aggregate d{{ .buffer_size = 100, .timeout = 2, .tries = 3 }}; // B (no extension beyond designated initializers)
non_aggregate e( .buffer_size = 100, .timeout = 2, .tries = 3 ); // B (implicit braces)
non_aggregate f{ .buffer_size = 100, .timeout = 2, .tries = 3 }; // error: braces are implicit only inside parens

It’s better to disallow the last case, because list-initialization should represent aggregation; it shouldn’t be used to express generating an object from a set of abstract parameters or constraints.

Nicol Bolas

unread,
Aug 19, 2015, 11:59:24 PM8/19/15
to ISO C++ Standard - Future Proposals


On Wednesday, August 19, 2015 at 9:59:08 PM UTC-4, David Krauss wrote:

On 2015–08–20, at 7:53 AM, Arthur O'Dwyer <arthur....@gmail.com> wrote:

On the subject of C and named parameters, I'll add that Ben Klemens' "21st Century C" shows a very nice idiom for getting Python-style named parameters out of a C99 compiler. I'm paraphrasing from memory a bit in the code sample below…

Perhaps the syntax of an expression-list beginning with “.” should indicate implicit braces, added as sugar. I think that passing a struct is a superior design when lots of parameters are present, and one supported by decades of experience.

Generally speaking, if you have that many parameters, then some of those parameters almost certainly are pieces of larger semantic groupings. And therefore they should have been grouped into structs to begin with.

What you're doing is enforcing a single struct policy, which is not something I would want to see advocated or become commonplace. However, it does lack the terrible syntactic noise of the C version, so it's already ahead of the curve ;)

The big downside I think has to do with the language. Your proposal makes sense, but not once you start providing default parameters. Here's an example:

parameters value{.buffer_size = 100, .timeout = 2, .tries = 3};

What does this do? I don't me from a top-level perspective; I'm not asking what you want it to mean. I'm asking what this actually does, in terms of the language?

That braced-init-list cannot perform aggregate initialization, because `parameters` is not an aggregate. Aggregates cannot have non-static data member initializers.

So what exactly are you proposing that this do? Do you intend to define a new class of pseudo-aggregate, which has all of the limitations of a regular aggregate, only it allows NSDMIs?

The problem with that is that NSDMI's operate by effectively modifying constructors. They provide default parameters for a constructor's initialization list; that's why they're not aggregates. If there are no user-provided constructors, and you're not using the default constructor (or one of the other special ones), it's not clear at all to me what code will be used to initialize the pseudo-aggregate. Where does the constructor you call get defined?

Oh sure, we all know what we want to have happen. It's a matter of making it work in the language without breaking 20 other things.

As for the implicit braces thing, that sounds like a very bad idea. Uniform initialization already has way too many implicit braces, to the point where it actually gets non-uniform. It's two extra characters; I don't see it as provoking that much syntactic noise.

Vicente J. Botet Escriba

unread,
Aug 20, 2015, 1:53:59 AM8/20/15
to std-pr...@isocpp.org
Le 15/08/15 18:49, Arthur Tchaikovsky a écrit :
Hi, 
Does anyone see helpfulness of such solution:
Having fnc:

void f(int a = 0, int b = 1, int c = 2, int d = 3);

I think that it would be nice to be able to say whilst calling it and 
requiring only some of those args to be non-default:

f(default,2,default,4);

Thoughts?


Hi, note that I'm not against the proposal.

Just wanted to share a way to do what you are proposing in C++14. It is an alternative that is cumbersome and error prone. It consist in using overloading and a default_t type and a default_ instance of default_t.


    f(int buffer_size = 1024, int timeout = 10, int tries = 3);
    f(default_t, int timeout = 10, int tries = 3) { return f(1024, timeout, tries); }
    f(int buffer_size, default_t, int tries = 3) { return f(buffer_size, 10, tries); }

f();
f(default_, 0);
f(default_, default_, 20);

The function provider can use constant for the defaults of course but the useof the constants is however error prone.

IMO, this kind of approach should appear in a possible proposal as your proposal solves all the issues of this approach.  

Vicente





David Krauss

unread,
Aug 20, 2015, 3:11:02 AM8/20/15
to ISO C++ Standard - Future Proposals
On 2015–08–20, at 11:59 AM, Nicol Bolas <jmck...@gmail.com> wrote:

What you're doing is enforcing a single struct policy, which is not something I would want to see advocated or become commonplace. However, it does lack the terrible syntactic noise of the C version, so it's already ahead of the curve ;)

One is better than zero… but what’s the impediment to grouping by nested sub-aggregates?

The problem with that is that NSDMI's operate by effectively modifying constructors.

Since C++14, NSDMIs have a second method of action, by aggregate initialization. They are already allowed in aggregates.

As for the implicit braces thing, that sounds like a very bad idea. Uniform initialization already has way too many implicit braces, to the point where it actually gets non-uniform. It's two extra characters; I don't see it as provoking that much syntactic noise.

Hmm, are you for the proposal or against? Implicit braces are the entirety of it. (Note that Clang and GCC already implement designated initializers, both only issuing a warning under -pedantic.) And they specifically don’t apply under brace (uniform) initialization.

The problem with adding designated initializers alone is that they enhance aggregates but not classes with constructors. This impedes refactoring an aggregate into a non-aggregate, which is a very common operation.

If you look closer, there is a common syntax (already compatible with GCC and Clang for initializing an aggregate with designated initializers or a non-aggregate by a “value prototype” aggregate, which is to use both parens and braces as foo({.x = 42}). It’s not that much typing, but it’s too much to be idiomatic, especially for a quick-and-dirty aggregate that just might become more complex later.

So, my suggestion is to use parens with designated initializers as a common ground between aggregates and constructors. If you’re writing an aggregate that perhaps shouldn’t be an aggregate, it encourages you to use parens and designated initializers. This should have the added benefit of reducing abuse of “uniform” initialization to call ordinary constructors. On the other hand, most such aggregates only have 2-3 members, and splitting out the parameter class is a bit of extra effort and noise.

Arthur Tchaikovsky

unread,
Aug 20, 2015, 5:11:43 AM8/20/15
to ISO C++ Standard - Future Proposals
I would urge you to read my post again and not the smily icon at the end of my sentece, and just mention, that as well as I made mistake judging @dgutson's sense of humor you've made identical mistake not realizing that I'm simply joking with David.

Arthur Tchaikovsky

unread,
Aug 20, 2015, 5:20:32 AM8/20/15
to ISO C++ Standard - Future Proposals
First, I want to say that I don't mind harsh, not harsh, too harsh words as long as their valid and I welcome any *valid* criticism and I consider your input very, very valid. No problem with that and I will not get offended by such thing. OK, let's go back to the business, point by point.
Andrey, you're saying that you don't see connection between default access mode and encapsulation. Could you please give an example or two, so we can see how that reflects reality? Because at this moment I'm having hard time to accept it, given that we're talking about the same thing.
OK, second point. You're saying and I quote:

"And no, all-public structs do not violate encapsulation in general. For
instance, std::pair - a struct with public data, does not break
encapsulation of std::(unordered_)map. "
Again, why would unrelated type break encapsulation of other type? I really don't see connection, in the same way I don't expect a container of ints to automatically provide ++, -- and other operators. And also please note that std::pair **do not** provide encapsulation. Because you can access it's members.

OK, so you're asking me, what's my point then? My point is that I do understand the fact that OOP is not the only technique we should use during programming, but I **do not agree** with introduction of rules to the C++ language that would allow to *bypass* that encapsulation.
Thank you.

Arthur Tchaikovsky

unread,
Aug 20, 2015, 5:57:11 AM8/20/15
to ISO C++ Standard - Future Proposals
Hi Vincente,
Thank you for your words.
As you yourself noted, the alternative is cumbersome and error prone. On top of that, there is a huge amount of work left to the designer/implementer of said function to provide all valid overloads which I'm not sure that if real world devs would really go to such extend and do it. With my proposal, all this burden, that is cumbersomeness, error proneness and a work of developer/designer of such function are simply removed. All is left is nice, natural syntax for users to use if they wish to. I know that is my proposal and I shouldn't really say it, but I genuinely fail to see bad/weak points of my proposal. It fits so nicely with C++ both philosophy (give a choice) and syntax that I really hope this will get voted into C++.

Thank you.

Nicol Bolas

unread,
Aug 20, 2015, 9:57:11 AM8/20/15
to ISO C++ Standard - Future Proposals
On Thursday, August 20, 2015 at 3:11:02 AM UTC-4, David Krauss wrote:

On 2015–08–20, at 11:59 AM, Nicol Bolas <jmck...@gmail.com> wrote:

What you're doing is enforcing a single struct policy, which is not something I would want to see advocated or become commonplace. However, it does lack the terrible syntactic noise of the C version, so it's already ahead of the curve ;)

One is better than zero… but what’s the impediment to grouping by nested sub-aggregates?

The problem with that is that NSDMI's operate by effectively modifying constructors.

Since C++14, NSDMIs have a second method of action, by aggregate initialization. They are already allowed in aggregates.

I did not know this. I'm not sure how I missed that one, since my browser history clearly showed that I found N3653...
 

As for the implicit braces thing, that sounds like a very bad idea. Uniform initialization already has way too many implicit braces, to the point where it actually gets non-uniform. It's two extra characters; I don't see it as provoking that much syntactic noise.

Hmm, are you for the proposal or against? Implicit braces are the entirety of it. (Note that Clang and GCC already implement designated initializers, both only issuing a warning under -pedantic.)

The fact that Clang and GCC implement designated inititalizers does not make them actual features of the C++ standard. So you can't propose a change that requires non-standard functionality.

Thus, this proposal must include designated initializers for aggregates.
 
And they specifically don’t apply under brace (uniform) initialization.

I don't know which "they" you're referring to: designated initializers or brace elision. I'm going to assume that you mean to say that designated initializers should be restricted to function calls, without explicit braced-init-lists. If that's not the case, then just ignore this section.

Given that, I like it even less now. You're devising very special-case syntax when there's no real need for it. Plus even though you're initializing an object, you explicitly cannot use uniform initialization syntax for it.

You're making uniform initialization even less uniform than it already was. Do we really need to do that?

I think your idea is imposing limitations on this functionality that just don't need to exist. Just provide designated initializers in C++ aggregate initialization syntax, period. Since NSDMIs in C++14 don't prevent something from being an aggregate or undergoing aggregate initialization, just proceed forward from there. Not only do you get something like named parameters, you also get generalized designated initalizers for aggregate initialization.

Oh sure, you'll still need the braces: `f({...})`. But that's fine, since it makes it clear to the user exactly what you're doing.

By requiring the braces, by making the object initialization more clear, it also solves the function overloading problem, since generalized designated initializer syntax would give you the ability to specify which set of parameters to use:

f(param_set1{...});
f
(param_set2{...});

I don't like the idea of hacking up a "solution" to named parameters this way (admittedly, a solution that many other languages use). But I'd be much happier with this if such a solution was just a natural outgrowth of a generally useful feature like designated aggregate initializers.

That is, you get designated initializers, which could be used for named parameters, rather than getting named parameters by a very restricted use of designated initializers.

It feels less like a hack that way.

The problem with adding designated initializers alone is that they enhance aggregates but not classes with constructors. This impedes refactoring an aggregate into a non-aggregate, which is a very common operation.

That is something of a danger, yes. But I would rather find a way to mitigate that problem than to deliberately gimp the functionality.

Here's a possible solution:

If the initializer list contains designated initializers, then it will attempt to use aggregate initialization on the target object type. If the target object is not an aggregate, then it will check all of the constructors for the target. If it finds a constructor that takes only one parameter (minus defaults of course) which is an aggregate, then it will initialize an aggregate and pass it to that constructor.

You could even recursively apply those rules. If a constructor takes one argument that has a constructor that takes one argument that has a constructor that takes one argument that is an aggregate, then the initializer list constructs that type, passes it to the next object, then passes the result to the next, and so on.

That way, when you refactor an aggregate into a non-aggregate, all you need to do is create a throwaway aggregate that matches the old aggregate, which your now-non-aggregate type uses for named parameter object initialization.

Thus, any non-aggregate could be initialized like this:

non_aggregate nagg = {.param1 = 20, .param3 = 50};

Yeah, this suggestion effectively uses brace elision, which I despise. But it does solve the problem, and it gives us nice functionality that your more limited approach would not have provided.

David Krauss

unread,
Aug 20, 2015, 8:49:51 PM8/20/15
to std-pr...@isocpp.org
On 2015–08–20, at 9:57 PM, Nicol Bolas <jmck...@gmail.com> wrote:

Thus, this proposal must include designated initializers for aggregates.

Or wait for someone else to propose designated initializers.

And they specifically don’t apply under brace (uniform) initialization.

I don't know which "they" you're referring to: designated initializers or brace elision.

“They” are implicit braces. Designated initializers and brace elision only work inside braces, and it should be obvious that I’m not trying to throw away everything.

I'm going to assume that you mean to say that designated initializers should be restricted to function calls, without explicit braced-init-lists. If that's not the case, then just ignore this section.

The suggestion is that, given a workable proposal for designated initializers (inside braced-init-lists) reflecting the implementation consensus, we solve the issue of providing designators to constructors and ordinary functions (inside parens) by adding implicit braces immediately inside the parens.

I don't like the idea of hacking up a "solution" to named parameters this way (admittedly, a solution that many other languages use). But I'd be much happier with this if such a solution was just a natural outgrowth of a generally useful feature like designated aggregate initializers.

My recollection is that there’s committee resistance to adding features to aggregates but not to constructors. I’ve not reviewed the minutes to verify this, but that’s the direction I’m coming from. Personally, I also feel that designated initializers are already overdue for C++ standardization, but a broader proposal stands better chances by addressing such concerns.

Here's a possible solution:

If the initializer list contains designated initializers, then it will attempt to use aggregate initialization on the target object type. If the target object is not an aggregate, then it will check all of the constructors for the target. If it finds a constructor that takes only one parameter (minus defaults of course) which is an aggregate, then it will initialize an aggregate and pass it to that constructor.

This occurred to me too, but

1. It allows conversion to look just like direct-initialization. It requires that a constructor call look like aggregate initialization, which I believe should be discouraged.

2. It’s essentially a brace elision rule, and as you mentioned that area is already complicated. Specifically, it’s a rule for omitting one outer level of braces, but the existing elision rule is based on descending immediately to the innermost level.

3. It doesn’t address named function arguments, which is really the more common request.

You could even recursively apply those rules. If a constructor takes one argument that has a constructor that takes one argument that has a constructor that takes one argument that is an aggregate, then the initializer list constructs that type, passes it to the next object, then passes the result to the next, and so on.

C++ doesn’t usually allow chained conversions. The programmer can easily generate a combinatorial explosion, if each potential step of the chain has multiple converting constructors.

robi...@gmail.com

unread,
Aug 20, 2015, 11:13:22 PM8/20/15
to ISO C++ Standard - Future Proposals
Elegant. I'm tired of function overload permutations and work-around tricks like chained setters or passing structures, when all I really want sometimes is the default value of a defaultable parameter which isn't the last parameter. It's nice to see that two other people have independently come to the same idea, you and Daniel Gutson (http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2003/n1466.pdf). While named parameters may be even nicer in general, the two proposals are not mutually exclusive.

For example, when I need to update the formatting where most of the parameters are defaultable, I can just say:

    ChangeFormat(
        L"Segoe UI"

        );

But that one time I actually need to set an explicit stretch, then I need to specify all the parameters before it: :/

    ChangeFormat(
        L"Segoe UI",
        DWRITE_FONT_WEIGHT_NORMAL,
        DWRITE_FONT_STYLE_NORMAL,
        DWRITE_FONT_STRETCH_CONDENSED
        );

When what I really want is simply:

    ChangeFormat(
        L"Segoe UI",
        default,
        default,
        DWRITE_FONT_STRETCH_CONDENSED
        );

You could even combine named parameters with default values (again, they are not mutually exclusive proposals):

    ChangeFormat(
        fontFamily: L"Segoe UI",
        fontWeight: default,
        fontSlope: default,
        fontStretch: DWRITE_FONT_STRETCH_CONDENSED
        );

robi...@gmail.com

unread,
Aug 20, 2015, 11:17:07 PM8/20/15
to ISO C++ Standard - Future Proposals
Nice read. Wish this or something similar (I might choose something other than angle brackets for overload disambiguation) had been voted in years ago.

Arthur Tchaikovsky

unread,
Aug 24, 2015, 5:13:03 AM8/24/15
to ISO C++ Standard - Future Proposals, robi...@gmail.com
Hi everyone,
I believe that this proposal, however small it is should be voted into C++, and judging by the comments here, it can safely be said that most people see value in it.
Could some committee member clarify with me what should happen next?

Thank you.

Daniel Krügler

unread,
Aug 24, 2015, 5:25:42 AM8/24/15
to std-pr...@isocpp.org, robi...@gmail.com
I'm not a committee member, but I can ensure you that if you want to
get progress here, the right way is to submit a paper targetting

Programming Language C++, Evolution Working Group

(because it describes a new feature to the core language).

For general guidance please take a look at

https://isocpp.org/std/submit-a-proposal

The paper should be send to the lwgchair address (use the email
address mentioned in the header of
http://www.open-std.org/jtc1/sc22/wg21/docs/lwg-active.html). Any
further procedural questions should also be send to the lwgchair
address (Presumably it's me who responds, nonetheless please send the
email always to the lwgchair address, not to my personal email
address).

Please note that the next mailing deadline (always from this page:
http://www.open-std.org/jtc1/sc22/wg21/) is 2015-09-25.

Thanks,

- Daniel

Arthur Tchaikovsky

unread,
Aug 24, 2015, 6:03:42 AM8/24/15
to ISO C++ Standard - Future Proposals, robi...@gmail.com
Thanks Daniel
Will do my best.

Regards
Reply all
Reply to author
Forward
0 new messages