Uniform initialization

295 views
Skip to first unread message

jspha...@gmail.com

unread,
Apr 18, 2015, 11:54:40 AM4/18/15
to std-pr...@isocpp.org
Can the C++ language disambiguate between initializer list and constructor call using @:

MyType myTypeVar = @{...}; //constructor call
vector<T> vec ={...}; //use current semantics

Alexander Marinov

unread,
Apr 18, 2015, 12:25:35 PM4/18/15
to std-pr...@isocpp.org
I would rather suggest removing 'std::initializer_list' as it can be fully replaced by using arrays. It only creates confusion and pollutes the new initialization syntax.

Thiago Macieira

unread,
Apr 18, 2015, 4:53:42 PM4/18/15
to std-pr...@isocpp.org
On Saturday 18 April 2015 09:25:35 Alexander Marinov wrote:
> I would rather suggest removing 'std::initializer_list' as it can be fully
> replaced by using arrays. It only creates confusion and pollutes the new
> initialization syntax.

Please explain how initializer_list can be replaced by arrays.
--
Thiago Macieira - thiago (AT) macieira.info - thiago (AT) kde.org
Software Architect - Intel Open Source Technology Center
PGP/GPG: 0x6EF45358; fingerprint:
E067 918B B660 DBD1 105C 966C 33F5 F005 6EF4 5358

Alexander Marinov

unread,
Apr 18, 2015, 5:02:40 PM4/18/15
to std-pr...@isocpp.org
Well - very simple. You just need to replace std::initializer_list with std::array.

Alexander Marinov

unread,
Apr 18, 2015, 5:02:46 PM4/18/15
to std-pr...@isocpp.org
Well - very simple. You just need to replace std::initializer_list with std::array.


събота, 18 април 2015 г., 23:53:42 UTC+3, Thiago Macieira написа:

Thiago Macieira

unread,
Apr 18, 2015, 5:05:21 PM4/18/15
to std-pr...@isocpp.org
On Saturday 18 April 2015 14:02:40 Alexander Marinov wrote:
> Well - very simple. You just need to replace *std::initializer_list *with
> *std::array*.

Will that work for constexpr initialisation, without allocating memory?

Alexander Marinov

unread,
Apr 18, 2015, 5:09:05 PM4/18/15
to std-pr...@isocpp.org
It should work all the cases that *std::initializer_list* do.

Nevin Liber

unread,
Apr 18, 2015, 5:18:25 PM4/18/15
to std-pr...@isocpp.org
On 18 April 2015 at 16:09, Alexander Marinov <sash...@mail.bg> wrote:
It should work all the cases that *std::initializer_list* do.

  1. Why??
  2. What are the motivating use cases for initializer_list being bad and array being good?
  3. array has different copy semantics.
  4. Using array would require all the constructors that use it to be in the header, as you'd have to templatize on the size of the array.
  5. Huge breaking change.
 

--

---
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/.



--
 Nevin ":-)" Liber  <mailto:ne...@eviloverlord.com(847) 691-1404

Alexander Marinov

unread,
Apr 18, 2015, 5:42:38 PM4/18/15
to std-pr...@isocpp.org
Yeah after ISO released them. Maybe you're right. Then perhaps it will be better to modify the 'std::vector' class instead. I don't think adding new syntax for such a small thing will be accepted.

Olanrewaju Adetula

unread,
Apr 18, 2015, 5:51:03 PM4/18/15
to std-pr...@isocpp.org

Changing the vector class will not necessarily solve inherent ambiguity because initializer list has preference over constructor calls.
Initializer can always be called by mistake if the intended parameters are the same type, although such cases are small I believe but having such a syntax will make the programmer's intention clear

Richard Smith

unread,
Apr 18, 2015, 7:00:26 PM4/18/15
to std-pr...@isocpp.org
On Sat, Apr 18, 2015 at 2:02 PM, Alexander Marinov <sash...@mail.bg> wrote:
Well - very simple. You just need to replace std::initializer_list with std::array.

The design of std::initializer_list deliberately avoids including the size of the list in the type, so you don't need to write a constructor template to handle a list of arbitrary length. std::array doesn't satisfy this use case.
 
събота, 18 април 2015 г., 23:53:42 UTC+3, Thiago Macieira написа:
On Saturday 18 April 2015 09:25:35 Alexander Marinov wrote:
> I would rather suggest removing 'std::initializer_list' as it can be fully
> replaced by using arrays. It only creates confusion and pollutes the new
> initialization syntax.

Please explain how initializer_list can be replaced by arrays.
--
Thiago Macieira - thiago (AT) macieira.info - thiago (AT) kde.org
   Software Architect - Intel Open Source Technology Center
      PGP/GPG: 0x6EF45358; fingerprint:
      E067 918B B660 DBD1 105C  966C 33F5 F005 6EF4 5358

David Rodríguez Ibeas

unread,
Apr 20, 2015, 4:36:41 AM4/20/15
to std-pr...@isocpp.org
Since we are throwing wild, hard or impossible to do ideas around 'std::initializer_list', what about making it a bit more cumbersome to use:

std::vector<int> a{{1, 2}}; // vector with contents [ 1, 2 ]
std::vector<int> b{1, 2};   // vector with contents [ 2 ]

Why? Just to bother people... not really. This would make the use of initializer lists slightly more cumbersome (not much, once you are typing the first {, pressing twice is not a huge deal and the same goes for }) but allow clearer distinction of this versus non-initializer lists arguments, which would be helpful in the context of some papers like N4029, by allowing the selection of a non-initializer list constructor when an initializer-list is present:

struct A {
   A(double);
   A(std::initializer_list<int>);
   A(int);
};
A f() {
   return { 1. };   // error: narrowing conversion from double to int -> could be legal
}
A g() {
   return {{ 1 }}; // initializer_list constructor
}
A h() {
    return { 1 }; // int constructor
}

But as I mentioned before, probably too much of a breaking change, as it would change the semantics of existing programs:

std::vector<int> f() {
    return { 5 };         // returns [ 10 ], would return [ 0, 0, 0, 0, 0 ]
}

... nevertheless I wish this had been the initial design.

    David

Roman Perepelitsa

unread,
Apr 20, 2015, 4:49:11 AM4/20/15
to std-pr...@isocpp.org
On Mon, Apr 20, 2015 at 10:36 AM, David Rodríguez Ibeas <dib...@ieee.org> wrote:
Since we are throwing wild, hard or impossible to do ideas around 'std::initializer_list', what about making it a bit more cumbersome to use:

std::vector<int> a{{1, 2}}; // vector with contents [ 1, 2 ]
std::vector<int> b{1, 2};   // vector with contents [ 2 ]

That's how it should have been done IMO. All problems caused by initializer lists stem from their magic ability of inserting an extra pair of curly braces. As you correctly pointed out, this aspect is unlikely to change.

Roman Perepelitsa.

Olanrewaju Adetula

unread,
Apr 20, 2015, 7:57:41 AM4/20/15
to std-pr...@isocpp.org

Since initializer list currently takes precedence, then adapting {{}} for strict initialization would be better.
It will eventually even make more sense in my opinion because {{}} can lead to subtle confusion when working with maps as that can easily lead to three { for the first key-value pair

--

David Krauss

unread,
Apr 20, 2015, 9:59:23 AM4/20/15
to std-pr...@isocpp.org
On 2015–04–20, at 4:36 PM, David Rodríguez Ibeas <dib...@ieee.org> wrote:

Since we are throwing wild, hard or impossible to do ideas around 'std::initializer_list', what about making it a bit more cumbersome to use:

std::vector<int> a{{1, 2}}; // vector with contents [ 1, 2 ]
std::vector<int> b{1, 2};   // vector with contents [ 2 ]

Why? Just to bother people... not really. This would make the use of initializer lists slightly more cumbersome (not much, once you are typing the first {, pressing twice is not a huge deal and the same goes for }) but allow clearer distinction of this versus non-initializer lists arguments,

You’re almost proposing deprecation of direct-list-initialization, if the intent is to make braces and parens behave exactly the same. The only obvious remaining difference would be that it works with aggregates, but it’s been suggested (by Richard, quite reasonably IMHO) that parens work for them too, at least at the top level. Is there anything else?

which would be helpful in the context of some papers like N4029, by allowing the selection of a non-initializer list constructor when an initializer-list is present:

A g() {
   return {{ 1 }}; // initializer_list constructor
}
A h() {
    return { 1 }; // int constructor
}

Braces tend to represent, or at least look like, aggregation. When an initializer_list constructor is present, it usurps the braces because the class is usually representing an aggregation of objects. It’s a bit of abuse to return braces as a universal shortcut for other sorts of initialization parameters. It’s unfortunate that good style must be violated to return a non-movable object, since braced-init-list conversion is the only way, but in other cases the keystrokes of the type-name are worth it.

(By the way, all your examples currently work with the removal of the outermost braces. The int and double constructors perform implicit conversions.)

inkwizyt...@gmail.com

unread,
Apr 20, 2015, 12:59:56 PM4/20/15
to std-pr...@isocpp.org
How about reusing `explicit` keyword there? It will ban special behavior of `std::initializer_list` in that type.

Olanrewaju Adetula

unread,
Feb 18, 2016, 6:35:56 PM2/18/16
to ISO C++ Standard - Future Proposals
how about differentiating between initializer list and constructor by allowing for trailing comma after after the last element for an initializer list with a compile time error if no initializer list is found 

vector<int> ai = { 5, };  //initializer list
vector<int> ai = { 5 };  //constructor call

Ville Voutilainen

unread,
Feb 18, 2016, 6:39:17 PM2/18/16
to ISO C++ Standard - Future Proposals
That was suggested at one point as a way to disambiguate, but it got rejected
as a disambiguation means. It might be possible to try getting such a
tweak accepted.

David Krauss

unread,
Feb 18, 2016, 8:59:54 PM2/18/16
to std-pr...@isocpp.org
This particular tweak is a massive breaking change.

Looking back over this thread, I see neither a clear motivation nor a mention of the current pattern: Braces tend to represent value aggregation and parens tend to represent computation by a function.

If the motivation is to get a constructor call without naming the type, how about the syntax auto(expression-list) to deduce (and explicitly cast to) a type from the given context?

The debate about implicit vs. explicit was contentious. As an implicit-to-explicit operator, this might help to find some middle ground.

struct very_long_name {
    explicit very_long_name( int, int, int );
    very_long_name( std::initializer_list< int > );
};

template< typename t >
void f( t && );

s factory() {
    return { 1, 2, 3 }; // initializer list
    return auto( 1, 2, 3 ); // constructor
    return flag? very_long_name( 1, 2, 3 ) : auto{ 1, 2, 3 }; // either initializer list or constructor
    f( auto{ 1, 2, 3 } ); // shortcut for initializer_list
}

The syntax auto(expr) has also been suggested to perform lvalue-to-rvalue conversion, but that can be done well enough (and with a better name) by a library facility.

Johannes Schaub

unread,
Feb 19, 2016, 5:10:51 AM2/19/16
to std-pr...@isocpp.org

An alternative syntax to consider would be the explosed-initializer-list which will prefer to expand its elements into separate constructor arguments

}a,b,c{

Ville Voutilainen

unread,
Feb 19, 2016, 5:14:00 AM2/19/16
to ISO C++ Standard - Future Proposals
On 19 February 2016 at 03:59, David Krauss <pot...@gmail.com> wrote:
>
> On 2016–02–19, at 7:39 AM, Ville Voutilainen <ville.vo...@gmail.com>
> wrote:
>
> On 19 February 2016 at 01:35, Olanrewaju Adetula <jspha...@gmail.com>
> wrote:
>
> how about differentiating between initializer list and constructor by
> allowing for trailing comma after after the last element for an initializer
> list with a compile time error if no initializer list is found
>
> vector<int> ai = { 5, }; //initializer list
> vector<int> ai = { 5 }; //constructor call
>
>
>
> That was suggested at one point as a way to disambiguate, but it got
> rejected
> as a disambiguation means. It might be possible to try getting such a
> tweak accepted.
>
>
> This particular tweak is a massive breaking change.

Oh, I was talking about making a trailing comma mean an initializer list, not
about making the latter form always a constructor call. The suggestion
for the latter form is indeed a breaking change so massive that it is
unattainable, but changing the meaning of a trailing comma might not be
massive. Maybe.

Olanrewaju Adetula

unread,
Feb 19, 2016, 2:13:02 PM2/19/16
to ISO C++ Standard - Future Proposals

Sorry, I meant using trailing comma for disambiguation not making the latter always a constructor call.
That is why I used vector<int> as an example


--

---
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.

Nicol Bolas

unread,
Feb 20, 2016, 4:58:28 PM2/20/16
to ISO C++ Standard - Future Proposals
On Thursday, February 18, 2016 at 8:59:54 PM UTC-5, David Krauss wrote:

On 2016–02–19, at 7:39 AM, Ville Voutilainen <ville.vo...@gmail.com> wrote:

On 19 February 2016 at 01:35, Olanrewaju Adetula <jspha...@gmail.com> wrote:
how about differentiating between initializer list and constructor by
allowing for trailing comma after after the last element for an initializer
list with a compile time error if no initializer list is found

vector<int> ai = { 5, };  //initializer list
vector<int> ai = { 5 };  //constructor call


That was suggested at one point as a way to disambiguate, but it got rejected
as a disambiguation means. It might be possible to try getting such a
tweak accepted.

This particular tweak is a massive breaking change.

Looking back over this thread, I see neither a clear motivation nor a mention of the current pattern: Braces tend to represent value aggregation and parens tend to represent computation by a function.

Is that "the current pattern"? I thought the whole point of having braced-init-lists was to allow aggregation and construction to be identical. It is called "Uniform Initialization", after all.

The motivation is to be able to do this:

template<typename ...Ts>
T emplace
(Ts&& ...ts)
{
 
return T{std::forward<Ts>(ts)};
}

Where `T` can be any type. If it's an aggregate, then it will use aggregate initialization. If it is not an aggregate, then T must have a constructor that takes those exact arguments. None of the "if it matches an initializer_list consturctor" BS.

Note that std::allocator::construct does not use braced-init-lists. Indeed, in C++14, it was altered such that if T was an aggregate, it would use braced-init-lists, and if T was not, it would not use braced-init-lists.

So there is a clear need to be able to construct a type by either aggregate initialization or constructor call.

If the motivation is to get a constructor call without naming the type, how about the syntax auto(expression-list) to deduce (and explicitly cast to) a type from the given context?

The debate about implicit vs. explicit was contentious. As an implicit-to-explicit operator, this might help to find some middle ground.

struct very_long_name {
    explicit very_long_name( int, int, int );
    very_long_name( std::initializer_list< int > );
};

template< typename t >
void f( t && );

s factory() {
    return { 1, 2, 3 }; // initializer list
    return auto( 1, 2, 3 ); // constructor
    return flag? very_long_name( 1, 2, 3 ) : auto{ 1, 2, 3 }; // either initializer list or constructor
    f( auto{ 1, 2, 3 } ); // shortcut for initializer_list
}

The syntax auto(expr) has also been suggested to perform lvalue-to-rvalue conversion, but that can be done well enough (and with a better name) by a library facility.


Actually, this reminds me of a one-off idea I had at one point to allow `return <braced-init-list>` to work. I suggested `explicit <braced-init-list>`, but `auto {}` could work too.

We have two problems nowadays with braced-init-lists:

1) Unable to call explicit constructors without naming the type.

2) Initializer_list constructors taking priority when there is a conflict.

Having `auto {}` do both of these would probably be a good idea. I justify joining these two problems because most initializer_list constructors aren't marked explicit.

This should also allow you to do:

T t auto{};

That is, direct-list-initialization. Again, as a way to bypass initializer_list constructors.

David Krauss

unread,
Feb 21, 2016, 1:01:18 AM2/21/16
to std-pr...@isocpp.org
On Sun, Feb 21, 2016 at 5:58 AM, Nicol Bolas <jmck...@gmail.com> wrote:
On Thursday, February 18, 2016 at 8:59:54 PM UTC-5, David Krauss wrote:

Looking back over this thread, I see neither a clear motivation nor a mention of the current pattern: Braces tend to represent value aggregation and parens tend to represent computation by a function.

Is that "the current pattern"? I thought the whole point of having braced-init-lists was to allow aggregation and construction to be identical. It is called "Uniform Initialization", after all.

We have identical syntax for aggregation and construction as long as they don't conflict. When they do conflict, we have different syntaxes to disambiguate. It's your choice whether to maximize use of braces and relegate parens to special cases of ambiguity, or to generalize the pre-C++11 patterns and use braces for constructors only which perform something like conceptual aggregation.

The term "uniform initialization" was the name the original proposal, but it's falling out of use.
 
The motivation is to be able to do this:

template<typename ...Ts>
T emplace
(Ts&& ...ts)
{
 
return T{std::forward<Ts>(ts)};
}

Maybe that aligns with the original motivation, but it's not what current standard emplace members actually do. There is a proposal to use braces like that, as a fallback when parens don't work.

If changing it only for emplace and std::allocator would be too much, then changing it across the whole language would be worse.
 
Actually, this reminds me of a one-off idea I had at one point to allow `return <braced-init-list>` to work. I suggested `explicit <braced-init-list>`, but `auto {}` could work too.

I remember that too. The difference is just in choice of keyword, but the connotations are different: auto suggests to deduce something, and explicit suggests to adjust constructor lookup.
 
We have two problems nowadays with braced-init-lists:

1) Unable to call explicit constructors without naming the type.

2) Initializer_list constructors taking priority when there is a conflict.

Having `auto {}` do both of these would probably be a good idea. I justify joining these two problems because most initializer_list constructors aren't marked explicit.

This should also allow you to do:

T t auto{};

That is, direct-list-initialization. Again, as a way to bypass initializer_list constructors.

Any brace syntax that bypasses initializer_list would be extremely confusing. Especially one that appears to only be pulling in some harmless deduction.

If it hurts when you use braces absolutely everywhere, then don't do that. (The same goes for Almost Always Auto.) Such rules work well with unsurprising, simple programs. Once one strays from value semantics, the grammatical vocabulary needs to increase. (And besides being new, T t auto{} would be equally non-uniform and un-generic as using parens instead of braces!)

For std::vector, for example, there's simply no need to tell a beginner about the constructors other than std::initializer_list. It works fine to default-construct and then call resize or insert. If it's actually a vector of vectors, then write a loop — that's actually more efficient than duplicating a prototype vector. Shortcuts and terseness naturally tend to conflict with uniformity. Maximizing one might mean sacrificing the other.

Nicol Bolas

unread,
Feb 21, 2016, 12:46:42 PM2/21/16
to ISO C++ Standard - Future Proposals
On Sunday, February 21, 2016 at 1:01:18 AM UTC-5, David Krauss wrote:
On Sun, Feb 21, 2016 at 5:58 AM, Nicol Bolas <jmck...@gmail.com> wrote:
On Thursday, February 18, 2016 at 8:59:54 PM UTC-5, David Krauss wrote:

Looking back over this thread, I see neither a clear motivation nor a mention of the current pattern: Braces tend to represent value aggregation and parens tend to represent computation by a function.

Is that "the current pattern"? I thought the whole point of having braced-init-lists was to allow aggregation and construction to be identical. It is called "Uniform Initialization", after all.

We have identical syntax for aggregation and construction as long as they don't conflict. When they do conflict, we have different syntaxes to disambiguate. It's your choice whether to maximize use of braces and relegate parens to special cases of ambiguity, or to generalize the pre-C++11 patterns and use braces for constructors only which perform something like conceptual aggregation.

If you design a feature to do X, but then you find that it doesn't do X, you call that feature "broken". The correct response is to fix the feature.

The incorrect response is to pretend we really meant for it to do Y. Especially when we still need X.

Because of this broken part of uniform initialization, we've made initialization more complicated. This is the exact opposite of the goal.

Take your "conceptual aggregation" rule. Even if we all agreed on what that means, you still can't use it in generic code, because whether a type is "conceptual aggregation" or not is not something that can be deduced (we don't even have an `is_aggregate` trait). Even if we ignore that, there's still one insurmountable problem:

If your type isn't involved in "conceptual aggregation", then your type is still at risk for the most vexing parse. You know, the primary impetus for us creating a whole new initialization scheme.

So I would say that this "conceptual aggregation" rule is broken.

We still need X.

We have two problems nowadays with braced-init-lists:

1) Unable to call explicit constructors without naming the type.

2) Initializer_list constructors taking priority when there is a conflict.

Having `auto {}` do both of these would probably be a good idea. I justify joining these two problems because most initializer_list constructors aren't marked explicit.

This should also allow you to do:

T t auto{};

That is, direct-list-initialization. Again, as a way to bypass initializer_list constructors.

Any brace syntax that bypasses initializer_list would be extremely confusing.

To whom?

Especially one that appears to only be pulling in some harmless deduction.

If it hurts when you use braces absolutely everywhere, then don't do that.

So, if the encryption in your server is easily cracked, the response should be "don't encrypt your server with our product"? We got a buggy feature in C++11. The correct answer is not to claim that it's the user's fault for finding the bug.

The correct answer is to fix the feature, not pretend that the bug was intended.

(The same goes for Almost Always Auto.)

Again, you could just fix the language.
 
Such rules work well with unsurprising, simple programs. Once one strays from value semantics, the grammatical vocabulary needs to increase. (And besides being new, T t auto{} would be equally non-uniform and un-generic as using parens instead of braces!)

Exactly how would it be non-uniform? Every type T which is default constructible can have its default constructor called with that syntax. Whether T is an aggregate or a non-aggregate, this will work.

T t auto{5, 3};

Will always either perform aggregate initialization or call a constructor of T that takes 2 parameters. If T has no such constructor, or it cannot be aggregate initialized from those parameters, then it will fail. Therefore, it is a concept of such a generic function that the T be constructible with `auto{5, 3}`.

The problem with doing `T t{5, 3}` is that you don't know if `T` has an initializer list constructor that takes integers. If it does, then this will almost certainly do the wrong thing. Do you see the point? It's "non-uniform and un-generic" only because it is calling the wrong function.

There is no reason why we can't have one initialization syntax that always aggregate/constructor initializes in every case. We simply didn't get that in C++11, because of the bad decision to prioritize initializer_list constructors. And that was only done because we combined uniform initialization syntax (which Bjarne proposed) with the initializer_list proposal, all just so that people could avoid having to write an extra set of braces for array-like initialization.

I honestly don't care if it uses `auto`, `explicit`, `{. stuff}` or whatever. There is a need to be able to direct-list-initialize/aggregate initialize that will not prioritize initializer_list constructors.

For std::vector, for example, there's simply no need to tell a beginner about the constructors other than std::initializer_list. It works fine to default-construct and then call resize or insert. If it's actually a vector of vectors, then write a loop — that's actually more efficient than duplicating a prototype vector. Shortcuts and terseness naturally tend to conflict with uniformity. Maximizing one might mean sacrificing the other.

Nonsense. If uniform initialization had been done correctly, if it had been made uniform, we wouldn't have to sacrifice anything. Creating an array with a vector would simply be `vector<int> v = {{1, 2, 3}};` One set of extra brackets makes all the difference. If that had been how C++11 shipped, people would simply have gotten used to using the double braces for that. Generic could would be able to distinguish between constructor initialization and initializer list initialization.

It would have been uniform initialization.

We also wouldn't need special brace-elision rules to make `array<int, 3> a = {1, 2, 3};` work. The only place where you wouldn't use a double-brace would be an actual language array. And people should just stop using those ;)

Péter Radics

unread,
Feb 22, 2016, 3:38:00 AM2/22/16
to ISO C++ Standard - Future Proposals
Nonsense. If uniform initialization had been done correctly, if it had been made uniform, we wouldn't have to sacrifice anything. Creating an array with a vector would simply be `vector<int> v = {{1, 2, 3}};` One set of extra brackets makes all the difference. If that had been how C++11 shipped, people would simply have gotten used to using the double braces for that. Generic could would be able to distinguish between constructor initialization and initializer list initialization.

It would have been uniform initialization.

We also wouldn't need special brace-elision rules to make `array<int, 3> a = {1, 2, 3};` work. The only place where you wouldn't use a double-brace would be an actual language array. And people should just stop using those ;)

Agreed (almost) completely: I would go so far as to fix the syntax even by a breaking change: use the most natural syntax for the most common/important thing (uniform initialization), and use {{ }} for initializer_list ctors as Nicol described above. Don't introduce additional clutter (auto {}, etc) as a "fix" :(
 
Reply all
Reply to author
Forward
0 new messages