Something is better then nothing: Please, relax Initializer List use with operators!

245 views
Skip to first unread message

mihailn...@gmail.com

unread,
Sep 18, 2016, 8:41:39 AM9/18/16
to ISO C++ Standard - Future Proposals
Hello, in a recent search about why is Initializer List limited so much (cant be used in arithmetics, assignment, etc), I came to this post : http://stackoverflow.com/a/11445905/362515

Basically the problem is, Initializer List is impossible to work in any situation where operators are used - parsing such an expression will be to complex.

That is fine.

However support in limited, but well defined scenarios is possible and I believe the benefits will still outweigh the confusion why it is not working in some contexts. 
What is more there is already a confusion why it is not working in "similar contexts"! 

For example from the user perspective assignment and comparison are "similar" contexts of use - one is "are you that thing" the other is "become that thing". 
Needless to say the syntax is, as we know, dangerously close "==" vs "=".

If for instance is allowed only the type of  the RHS in operator to be deduced from an init-list and expression is dissuaded to start with a init-list, not only many if not all use cases will be possible, but it will be somewhat easier to teach why the other case is to available.

Back to the example - it is hard to explain why this is working:
Point p;
p
= {-1, -1};

but this is not:

if(p == {-1, -1}) return;


But this is actually relatively easy to explain why it's not working:
if({-1, -1} == p) return;

Basically the user will learn at some point that {,} by itself is nothing more the a bunch a character, shortcut to create something in given context - it is not an expression, not a value, not even an initializer_list<T>

He will see there is a difference b/w 
if(-1 == p.x)
 and
if({-1, -1} == p)

He will see that in the first case he is creating something concretely - he is saying 'create the object int from that -1 I gave you", but in the last case he is just saying "I am giving you two ints in a packet". 

The user clearly sees, he is not saying what to create with these two ints - might be a pair, a size, an array. The user knows he is underspecifying at that point. 

He just hopes the context is enough for the compiler to do the work.

At this point we just step in and say - no, you must give the context before you ask us to do the guessing!
We'll guess p == {1-, -1}, we'll guess p + {.5, 0}, we might even guess p = {10, 10} - p.
But we'll not guess an expression which starts with enigma and a context to solve it given later.

Thing is, the user will not be surprised by that rule. he knows it already -  this is the reason we have a trailing return type! 
For the user will not be something new if he is required to do something from left to right if he'll benefit (quite a lot) from it.  
"I will help you, if you help me" said the parser.

As said, in its current state init-list usage is both extremely limited and it is hard to explain why.
If the rules are relaxed it will be both much, much, much more useful and easier to explain why some usage is not allowed.

The point I am trying to make is - I don't consider "allowing initializer lists as right-hand, but nor as left-hand arguments to most operators is too much of a kludge".
Init-list, {,} is magic, it does not have to work in all cases, it is there to help you as much as it can. And now it works in roughly half of the cases although it can do much more. 
Please let it do more. (And actually making it more predictable in the process).

P.S
I must stress it is not just about "less typing", it is about less type commitment - Point might be QPoint next year or MyPoint the other, but it will always, even in a million years will be represented by two numbers.

Bonus:
This must work, it really must work:
const auto e = car ? car->engine() : {};
 We know it does not but it should, no reason why not - first arg is given, second arg is given, the the third must the same type as the second, so we have everything to deduce it!

 

D. B.

unread,
Sep 18, 2016, 8:48:48 AM9/18/16
to std-pr...@isocpp.org
Um, no. It works in functions etc because they know what type to expect. Arbitrary expression do not. So they should not attempt to guess what the user is meaning. Such guesses will be wrong far more than they would be right.

Nicol Bolas

unread,
Sep 18, 2016, 9:08:08 AM9/18/16
to ISO C++ Standard - Future Proposals, mihailn...@gmail.com
On Sunday, September 18, 2016 at 8:41:39 AM UTC-4, mihailn...@gmail.com wrote:
Hello, in a recent search about why is Initializer List limited so much (cant be used in arithmetics, assignment, etc), I came to this post : http://stackoverflow.com/a/11445905/362515

Basically the problem is, Initializer List is impossible to work in any situation where operators are used - parsing such an expression will be to complex.

That is fine.

However support in limited, but well defined scenarios is possible and I believe the benefits will still outweigh the confusion why it is not working in some contexts. 
What is more there is already a confusion why it is not working in "similar contexts"! 

For example from the user perspective assignment and comparison are "similar" contexts of use - one is "are you that thing" the other is "become that thing". 
Needless to say the syntax is, as we know, dangerously close "==" vs "=".

If for instance is allowed only the type of  the RHS in operator to be deduced from an init-list and expression is dissuaded to start with a init-list, not only many if not all use cases will be possible, but it will be somewhat easier to teach why the other case is to available.

Back to the example - it is hard to explain why this is working:
Point p;
p
= {-1, -1};

but this is not:

if(p == {-1, -1}) return;


Because one is assignment while the other is equality testing. They're two different things.

Why is that hard to explain?


But this is actually relatively easy to explain why it's not working:
if({-1, -1} == p) return;


Um, no. If we allow `X == Y` to work, then it makes sense that we allow `Y == X` to work. To disallow one and not the other is far more bizarre than allowing one operation but forbidding a completely different one.

Bonus:
This must work, it really must work:
const auto e = car ? car->engine() : {};
 We know it does not but it should, no reason why not - first arg is given, second arg is given, the the third must the same type as the second, so we have everything to deduce it!

Again, that goes back to consistency. `!car ? {} : car->engine()` is conceptually the same, yet it wouldn't work under your scheme.

mihailn...@gmail.com

unread,
Sep 18, 2016, 10:11:38 AM9/18/16
to ISO C++ Standard - Future Proposals, mihailn...@gmail.com
For a novice assignment and comparison are semantically and syntactically similar. I put "similar" in quotes. To explain why one does not work, but the other works is not easy, beyond "its different things, different operations". And "it is hard to do", which is the real answer. 

About symmetry - that is the main issue - to have symmetry is much, much, much less important then to have the init-list available to operators uses (at least in my opinion).
The reason I put so much 'much' is because there is clear and readable indication why it (the symmetry) does not work - the user knows (learns) the {,} does not instantiate an object by itself, so no "guessing" (overloading) can begin. This can not be said for the current state of affairs - there is no way to know in advance (for a novice) which operation will accepts {} and which not - the user will have to learn, through trail and error, which combo is hardcoded to work. 
Considering there is no expression which starts with {}, he will know(learn) naked {} will not get him anywhere, he'll have to add the type this time around. 
if(Point{-1, -1} == p) return;


As for the ternary, again - insisting on total symmetry kills the greater benefits. 

But I am arguing, users already know left to right, top to bottom is important in c and c++. 

The user already knows sometimes the compiler must be able to see what you are using before you are using it. 

In this case the user must show one type first, so the compiler can guess the other, left to right as always, an extension of the age-old rule.

Much like you can't call a function which is not declared before it is called or use an object #included after its place of usage - naked {} depends on some type to be provided first so it can do its magic (which is already the case for the limited cases it is used today). 

D. B.

unread,
Sep 18, 2016, 10:26:26 AM9/18/16
to std-pr...@isocpp.org
Not including a statement of intended type would be obfuscation, even if it didn't require your idea of a fundamental change to the language - to add asymmetry to comparison and ternary operators. If I may presume: that's just not going to happen. And I don't want it to.


there is no way to know in advance (for a novice) which operation will accepts {} and which not - the user will have to learn, through trail and error, which combo is hardcoded to work.

Yes, there is. The novice should train themselves in the language. Then it's very clear.

Your proposal, among other things, seems to introduce more esoteric hard-coded rules and more requirements for trial and error, not to mention more hassle for compiler authors.

The language already went through a rather huge change to make {} deduce to an std::initializer_list in cases where the type isn't unambiguously specified elsewhere. It is massively unlikely that they will revert that now;.

mihailn...@gmail.com

unread,
Sep 18, 2016, 11:22:16 AM9/18/16
to ISO C++ Standard - Future Proposals
Considering, 10 years later, there will be no permanent, "right" solution, as it seems, ever (not to mention it will undoubtedly be much more complex). 
I am willing to "sacrifice" symmetry to get 90%+ of the usage. I put quotes because I have never ever wanted to write the arguments in the revere order, to write a naked {} first. 

As for the usefulness for the feature itself, I think It is beyond a doubt. 
There are many, many places, in everyday code, not some obscure, write-once library, where semantically there is no ambiguity what one is trying to achieve, using the forbidden cases.

The original paper is also aware of it. It is only a technical limitation.


D. B.

unread,
Sep 18, 2016, 11:28:31 AM9/18/16
to std-pr...@isocpp.org
I am willing to "sacrifice" symmetry to get 90%+ of the usage.

That's nice for you. I'm not. But good luck convincing the Committee to your point of view.


The original paper is also aware of it. It is only a technical limitation.
 
It wasn't "only a technical limitation". They also thought that removing said limitation would be a kludge, from which I might infer they meant unintuitive and prone to ugly code:
We suspect that allowing initializer lists as right-hand, but nor [sic] as left-hand arguments to most operators is too much of a kludge


And I agree. But again, you're welcome to pursue support from other users if you wish.
 

mihailn...@gmail.com

unread,
Sep 18, 2016, 11:57:12 AM9/18/16
to ISO C++ Standard - Future Proposals
I meant, it is a technical limitation to have the full feature. 
Considering this technical limitation proved to be too great -  ".., we try to express a more general rule." never materialized in 10 years and the solution WILL be complex - I just wanted to suggest, It might be worth considering "concepts-light" edition on the issue. To make a reasonable, in my opinion, sacrifice to get well over 90% of this great feature. 

Johannes Schaub

unread,
Sep 18, 2016, 12:59:37 PM9/18/16
to std-pr...@isocpp.org

Am 18.09.2016 15:08 schrieb "Nicol Bolas" <jmck...@gmail.com>:
>
> On Sunday, September 18, 2016 at 8:41:39 AM UTC-4, mihailn...@gmail.com wrote:
>>
>> Hello, in a recent search about why is Initializer List limited so much (cant be used in arithmetics, assignment, etc), I came to this post : http://stackoverflow.com/a/11445905/362515
>>
>> Basically the problem is, Initializer List is impossible to work in any situation where operators are used - parsing such an expression will be to complex.
>>
>> That is fine.
>>
>> However support in limited, but well defined scenarios is possible and I believe the benefits will still outweigh the confusion why it is not working in some contexts. 
>> What is more there is already a confusion why it is not working in "similar contexts"! 
>>
>> For example from the user perspective assignment and comparison are "similar" contexts of use - one is "are you that thing" the other is "become that thing". 
>> Needless to say the syntax is, as we know, dangerously close "==" vs "=".
>>
>> If for instance is allowed only the type of  the RHS in operator to be deduced from an init-list and expression is dissuaded to start with a init-list, not only many if not all use cases will be possible, but it will be somewhat easier to teach why the other case is to available.
>>
>> Back to the example - it is hard to explain why this is working:
>> Point p;
>> p = {-1, -1};
>>
>> but this is not:
>>
>> if(p == {-1, -1}) return;
>>
>
> Because one is assignment while the other is equality testing. They're two different things.
>

You habe just explained it in terms of how the compiler deduces the illformedness. "one is this and one is that. And the language forbids that and now this". That is not an explanation of the "why" but more of the "how" i guess,

> Why is that hard to explain?
>
>>
>> But this is actually relatively easy to explain why it's not working:
>> if({-1, -1} == p) return;
>>
>
> Um, no. If we allow `X == Y` to work, then it makes sense that we allow `Y == X` to work. To disallow one and not the other is far more bizarre than allowing one operation but forbidding a completely different one.
>

I agree.

>> Bonus:
>> This must work, it really must work:
>> const auto e = car ? car->engine() : {};
>>  We know it does not but it should, no reason why not - first arg is given, second arg is given, the the third must the same type as the second, so we have everything to deduce it!
>
>
> Again, that goes back to consistency. `!car ? {} : car->engine()` is conceptually the same, yet it wouldn't work under your scheme.
>

Why? Two arg types are known. The third  arg types will just initialize a value of that one branches type.

This is conceptually remotely consistent with the treatment of init list as nondeduced contexts, in which the initlist will initialize a parameter type that is determined elsewhere.

Johannes Schaub

unread,
Sep 18, 2016, 1:04:01 PM9/18/16
to std-pr...@isocpp.org

Am 18.09.2016 15:08 schrieb "Nicol Bolas" <jmck...@gmail.com>:
>

> On Sunday, September 18, 2016 at 8:41:39 AM UTC-4, mihailn...@gmail.com wrote:
>>
>> Hello, in a recent search about why is Initializer List limited so much (cant be used in arithmetics, assignment, etc), I came to this post : http://stackoverflow.com/a/11445905/362515
>>
>> Basically the problem is, Initializer List is impossible to work in any situation where operators are used - parsing such an expression will be to complex.
>>
>> That is fine.
>>
>> However support in limited, but well defined scenarios is possible and I believe the benefits will still outweigh the confusion why it is not working in some contexts. 
>> What is more there is already a confusion why it is not working in "similar contexts"! 
>>
>> For example from the user perspective assignment and comparison are "similar" contexts of use - one is "are you that thing" the other is "become that thing". 
>> Needless to say the syntax is, as we know, dangerously close "==" vs "=".
>>
>> If for instance is allowed only the type of  the RHS in operator to be deduced from an init-list and expression is dissuaded to start with a init-list, not only many if not all use cases will be possible, but it will be somewhat easier to teach why the other case is to available.
>>
>> Back to the example - it is hard to explain why this is working:
>> Point p;
>> p = {-1, -1};
>>
>> but this is not:
>>
>> if(p == {-1, -1}) return;
>>
>
> Because one is assignment while the other is equality testing. They're two different things.
>
> Why is that hard to explain?
>
>>
>> But this is actually relatively easy to explain why it's not working:
>> if({-1, -1} == p) return;
>>
>
> Um, no. If we allow `X == Y` to work, then it makes sense that we allow `Y == X` to work. To disallow one and not the other is far more bizarre than allowing one operation but forbidding a completely different one.
>

However, C++ already has precedent of this asymmetry. Member operator overloads are already looked up only in the left hand side class object.

Johannes Schaub

unread,
Sep 18, 2016, 1:08:42 PM9/18/16
to std-pr...@isocpp.org

Am 18.09.2016 14:48 schrieb "D. B." <db0...@gmail.com>:
>
> Um, no. It works in functions etc because they know what type to expect. Arbitrary expression do not. So they should not attempt to guess what the user is meaning. Such guesses will be wrong far more than they would be right.
>
>

I don't see a need for guessing. The same procedure that apples to op= should apply to op@. The brace is treated as a function argument to overloaded or builtin operators. For example "3 + { 1.2 }" would be illformed because it contains a narrowing conversion (UAC will apply only after list initialization was done, to have both the cake and eat it).

mihailn...@gmail.com

unread,
Sep 18, 2016, 2:58:27 PM9/18/16
to ISO C++ Standard - Future Proposals

> Um, no. If we allow `X == Y` to work, then it makes sense that we allow `Y == X` to work. To disallow one and not the other is far more bizarre than allowing one operation but forbidding a completely different one.
>

However, C++ already has precedent of this asymmetry. Member operator overloads are already looked up only in the left hand side class object.

 
I want to stress out again, it is not true asymmetry. It is not like "a value of type Y cannot be compared (or looked up to select operator) to a value of type X, although the opposite is true". 

The case is - smart init-list overload deduction can be activated as long it is valid expression. 
{-1, -1} == p will not be considered a valid expression, because the parser will not be bothered to go backwards to do magic for you. 
You have two options then:
  1. if you want magic from me - arrange the code, so I can read it from left to right as you know I do for decades, the meaning is the same anyways! (You lose nothing and gain magic)
  2. give me type manually and forget about magic. (If you want deduction you can do decay_t<decltype()p>{-1, -1} )
Let me give the same example in a different form. 
//does not:
if(-1 == x)
//works:
if(x == -1)


This is true asymmetry, because we have valid statements: create integer with value -1, use x to call ==

However
if(p == {-1, -1})
//vs

if({-1, -1} == p)



BOTH
of these are completely different from the above int example! Both! 

And this is clear from the point of view of the user - he did not create any objects!
The user knows, even a beginner I argue, he started some sort of autodeduction! 

We are one level "above" the actual comparison, we are preparing for it, and the user knows it. 

Even a child will guess, "mr. Computer" at some point will have to decide if the {} is a Point or is it a Size I want to compare to.
And here, in this high realm, we introduce, 
"mr. Computer" does not look forward, then go back, then go forward again, you can't start asking for magic first, dear, you have to give something first.

"But why -1 == p worked?" (asked the child)
"Remember, you were very specific in your request, you did not ask magic from me, you gave me object type, you can do the same again, no problem. For magic, or do as I ask of you."

Yes, there is no symmetry in the higher realm. But this ream is new, there is nothing like it before, the rules can change (a bit) if the prize is magic. 

One somewhat related example - auto is magic, auto is a substitution of type, auto can be omitted or can be manually or substituted for a type. These are interchangeable, there is "symmetry"
Except when auto can't be substituted manually for a type!  

Nicol Bolas

unread,
Sep 18, 2016, 3:23:19 PM9/18/16
to ISO C++ Standard - Future Proposals, mihailn...@gmail.com
On Sunday, September 18, 2016 at 2:58:27 PM UTC-4, mihailn...@gmail.com wrote:

> Um, no. If we allow `X == Y` to work, then it makes sense that we allow `Y == X` to work. To disallow one and not the other is far more bizarre than allowing one operation but forbidding a completely different one.
>

However, C++ already has precedent of this asymmetry. Member operator overloads are already looked up only in the left hand side class object.

 
I want to stress out again, it is not true asymmetry. It is not like "a value of type Y cannot be compared (or looked up to select operator) to a value of type X, although the opposite is true". 

Wait a minute.

You want to have this feature, in your words, because it was "hard to explain why this is working: but this is not:". Yet your explanation of why one kind of asymmetry is not "true asymmetry" is based on the complex question of what is a syntactic failure and what is not. That is, what a "valid statement" is.

It seems to me that you're trading one form of hard to explain incongruity with another. And that's assuming we buy your belief that the current state of things is in fact hard to explain.

mihailn...@gmail.com

unread,
Sep 18, 2016, 5:35:27 PM9/18/16
to ISO C++ Standard - Future Proposals, mihailn...@gmail.com

Wait a minute.

You want to have this feature, in your words, because it was "hard to explain why this is working: but this is not:". Yet your explanation of why one kind of asymmetry is not "true asymmetry" is based on the complex question of what is a syntactic failure and what is not. That is, what a "valid statement" is.

It seems to me that you're trading one form of hard to explain incongruity with another. And that's assuming we buy your belief that the current state of things is in fact hard to explain.

This is what I want to trade: 

A relatively new, somewhat confusing, rule for the old confusing (but for different reasons) rule.
Making this "switch", however, grans considerable new functionality for everyday use for basically every programmer, especially in this day of age where simple objects are becoming popular. 

Staying with the old rule is fine, one will learn it. But will get nothing (new). 
(again I believe the usefulness of the general feature - deducing {,} statements, quirks or not - is beyond a doubt. If it wasn't important it would have not be implemented for the current cases already a decade ago. These are by all means not all useful cases and this is also not a secret)

Waiting for the the rule/implementation to solve all issues, without quirks, is unrealistic at this point. 


However, I also argue, not only is the new functionality a plus all by itself. The new quirk is way better to explain, because it is does not seem to be based on seemingly "illogical limitations" (from a user perspective!).

The user has only two things to know - 
 1) {,} means nothing by itself, nothing - it is a helper, a ready-made, for something "real", depending on the context. 
 2) This context must come first. ...but things must be written in order already in the entire language, even if it is inconvenient - it is nice to have function anywhere in a file but you can't - you must place it before you use it, it is nice to have return type of inner class by name only in a impl. file, but you can't - you must qualify it or make the return declaration as a trailing one (excluding the full auto magic, which is very new). The compiler will not go forward, then go backwards. 

None of these are truly new. Granted, the context where they appear is new, yes, the price for the lunch, but the context is also new as well - one can't truly compare to how we are used to, as I argued in the last post. 



Ville Voutilainen

unread,
Sep 19, 2016, 3:22:12 PM9/19/16
to ISO C++ Standard - Future Proposals
Here's what a compiler expert told me, after I asked for his input:

---snip---
> Why doesn't something like
>
> pair<int, int> p;
>
> if (p == {1,2})
>
> work? Is there some parsing difficulty involved? I guess making
>
> if ({1,2} == p)
>
> work might lead to parsing nightmares?


Yes, it would be a bit painful to specify, I think. We’d presumably
want an initializer-clause to become a valid primary-expression.
Expression can appear alone in statements (expression statements), but
statements can be blocks with embedded expression statements. So:

{ 1+2+3+4; }

would look like an expression until you hit the “;”. I suppose we
could evade that issue by only considering “{“ to start an expression
if no statement can start at that place.

I remember Bjarne discussing this in the early list-init papers, but I
don’t recall the details of the conclusion (I do seem to recall that
he right away decided to only allow them on the right hand side of
assignments, when it came to non-initializer contexts).

My gut feeling is that there isn’t an insurmountable problem with the
idea though (assuming we allow a leading “{“ in a statement to always
resolve to a block statement).
---snap---

mihailn...@gmail.com

unread,
Sep 20, 2016, 3:40:39 AM9/20/16
to ISO C++ Standard - Future Proposals
...

Here's what a compiler expert told me, after I asked for his input:

---snip---
> Why doesn't something like
>
> pair<int, int> p;
>
> if (p == {1,2})
>
> work? Is there some parsing difficulty involved? I guess making
>
> if ({1,2} == p)
>
> work might lead to parsing nightmares?


Yes, it would be a bit painful to specify, I think.  We’d presumably
want an initializer-clause to become a valid primary-expression.
Expression can appear alone in statements (expression statements), but
statements can be blocks with embedded expression statements.  So:

        { 1+2+3+4; }

would look like an expression until you hit the “;”.  I suppose we
could evade that issue by only considering “{“ to start an expression
if no statement can start at that place.

I remember Bjarne discussing this in the early list-init papers, but I
don’t recall the details of the conclusion (I do seem to recall that
he right away decided to only allow them on the right hand side of
assignments, when it came to non-initializer contexts).

My gut feeling is that there isn’t an insurmountable problem with the
idea though (assuming we allow a leading “{“ in a statement to always
resolve to a block statement).
---snap---
 
Yes, that's the point - let leading “{“ be a block statement as it always was.

In that sense the user will not have to learn something new! He already knows '{' does not start an expression!

It is worth also nothing, the deduction is more often needed and used on the RHS anyway.

Users write more often if(x == -1) and if(ptr != 0), then the other way around. 
The reverse needs pretty much be trained, for a specific reason, 
if someone has the training to reverse the args, he'll got used to specify the type anyway!

Also, some operations do not even make sense if reversed:

PointList list;

//expected and much needed behavior:
list
<< {-1, -1};

// useless anyway:
{-1, -1} << list;


Some will argue, this should work, it is not meaningless:

{-1, -1} >> list;

But, seriously, who writes code like that ?!? 

Matthew Woehlke

unread,
Sep 21, 2016, 12:06:16 PM9/21/16
to std-pr...@isocpp.org
On 2016-09-18 09:08, Nicol Bolas wrote:
> On Sunday, September 18, 2016 at 8:41:39 AM UTC-4, mihailn...@gmail.com
> wrote:
>> Back to the example - it is hard to explain why this is working:
>> Point p;
>> p = {-1, -1};
>>
>> but this is not:
>>
>> if(p == {-1, -1}) return;
>
> Because one is assignment while the other is equality testing. They're two
> different things.
>
> Why is that hard to explain?

...because it *doesn't make sense*. Consider:

foo.operator@({...}) // works for any operator
foo @ {...} // only works for '='

...where `@` is replaced with a valid operator, e.g. `==`, `*`, `+`,
etc. Why *should* it work with `=` but not other operators?

Consider also:

A a;
B b; // B is convertible to A

a.operator@(b); // works
a @ b; // works

Why do these mean the same *except* when `b` is replaced with an
initializer list *and* the operator is not `=`? That just feels like an
arbitrary rule that isn't otherwise consistent with the language.

>> *But this is actually relatively easy to explain why it's not working*:
>> if({-1, -1} == p) return;
>
> Um, no. If we allow `X == Y` to work, then it makes sense that we allow `Y
> == X` to work.

As Johannes already noted, your logic is faulty (non sequitor):

// Let 'a' be a complex type with 'bool operator==(bool) const'
a == true; // okay
true == a; // error

Operators — even the comparison operator — are not necessarily
commutative. (Similarly, I could readily create some types for which `(a
== b) != (b == a)`.)

I am *quite* happy to argue that `{...} == expr` should not be allowed.
More accurately, it should be allowed iff there exists a conversion from
`expr` to std::initializer_list *and* there exists an operator== for
std::initializer_list, just like if the LHS was any other type. (IOW,
since those conditions are false, it follows that the operation is not
permitted, *according to the same rules that apply to any other LHS
type*. This is therefore logical and consistent with the language as a
whole.)

> *Bonus:*
>> This must work, it really must work:
>> const auto e = car ? car->engine() : {};
>> We know it does not but it should, no reason why not - first arg is
>> given, second arg is given, the the third must the same type as the second,
>> so we have everything to deduce it!
>
> Again, that goes back to consistency. `!car ? {} : car->engine()` is
> conceptually the same, yet it wouldn't work under your scheme.

Why couldn't it? The compiler ought to be able to deduce what to do with
an initializer list as one half of a ternary, regardless of which half
it appears in.

The rule here is simple; if one "half" of a ternary is an initializer
list and the other is not, and there exists a conversion to the type of
the other "half" from an initializer list, then that conversion is
implicitly applied to the initializer list.

--
Matthew

mihailn...@gmail.com

unread,
Sep 21, 2016, 12:57:46 PM9/21/16
to ISO C++ Standard - Future Proposals, mwoehlk...@gmail.com
I am *quite* happy to argue that `{...} == expr` should not be allowed. 
More accurately, it should be allowed iff there exists a conversion from 
`expr` to std::initializer_list *and* there exists an operator== for 
std::initializer_list, just like if the LHS was any other type. (IOW, 
since those conditions are false, it follows that the operation is not 
permitted, *according to the same rules that apply to any other LHS 
type*. This is therefore logical and consistent with the language as a 
whole.) 

It should be noted the {,} statement is not an std::initializer_list all by itself (IIRC). It can be converted to such like in the auto list = {,};
Also beginning a statement with { is already a new scope.
AFAIK complications like these killed the use of init-list with operators 10 years ago.

But the fact it can't be used on the LHS should not forbid using it on the RHS - they are already rhs-only (were used in such context), AND are mostly needed there anyway!

I hope this, should I say, 'maximalist' viewpoint to be revisited. 

The perfect should not be the enemy of the good.

Matthew Woehlke

unread,
Sep 28, 2016, 9:43:02 AM9/28/16
to std-pr...@isocpp.org
On 2016-09-21 12:57, mihailn...@gmail.com wrote:
> It should be noted the {,} statement is not an std::initializer_list all by
> itself (IIRC). It can be converted to such like in the auto list = {,};
> Also beginning a statement with { is already a new scope.

If that was the only problem, it's easily solved (again, let `@` be some
operator):

auto{...} @ foo

...and of course it doesn't come up if the expression is in an `if()`,
or the RHS of an assignment (e.g. `auto x = {...} @ y`). So I don't
think this should kill using an initializer-list as an operand of an
expression like `x @ y`.

What *does* kill it (on the LHS) is that the left operand of `x @ y` is
what determines where `operator@` is looked up, and (at least presently)
std::initializer_list has no operators.

WHICH IS FINE!

> *But the fact it can't be used on the LHS should not forbid using it on the
> RHS* - they are already rhs-only (were used in such context), AND are
> mostly needed there anyway!

Exactly. I disagree with the assertion that we can't allow it on the RHS
because we can't allow it on the LHS. There is a perfectly valid and
consistent reason why it makes no sense on the LHS (see above), that is
true for *any* left operand, not just std::initializer_list.
Extrapolating from that that we should not allow it on the RHS either
does not follow.

> The perfect should not be the enemy of the good.

Agreed!

--
Matthew

Reply all
Reply to author
Forward
0 new messages