Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Initialiser list oddities

80 views
Skip to first unread message

bol...@cylonhq.com

unread,
Jun 25, 2018, 4:59:14 AM6/25/18
to
Why is it I can do the following (even in C)...

int i[] = { 1,2,3 }

but in C++ 2011 (and newer?) I still can't do:

void func(int i[])
{
}


func({ 1,2,3 })

Why did the standards committee not permit this but instead the standards
committee came up with the new std::initializer_list (spelt wrong for everyone
outside the USA too) class? Surely allowing both would be advantagious?

Just curious.



Paavo Helde

unread,
Jun 25, 2018, 7:19:18 AM6/25/18
to
On 25.06.2018 11:59, bol...@cylonHQ.com wrote:
> Why is it I can do the following (even in C)...
>
> int i[] = { 1,2,3 }
>
> but in C++ 2011 (and newer?) I still can't do:
>
> void func(int i[])
> {
> }
>
>
> func({ 1,2,3 })

This is because void func(int i[]) is the same than void func(int* i)
(by ancient C rules). How should the system know how to convert some
random initializer_list into a pointer.

As you see, the old C syntax is misleading and error-prone. Fortunately
in C++11 you can rewrite the above in a perfectly fine C++, with zero
overhead:

#include <array>

void func(const std::array<int,3>& i)
{
}

int main() {
func({ 1,2,3 });
}




Juha Nieminen

unread,
Jun 25, 2018, 8:56:24 AM6/25/18
to
Paavo Helde <myfir...@osa.pri.ee> wrote:
> As you see, the old C syntax is misleading and error-prone. Fortunately
> in C++11 you can rewrite the above in a perfectly fine C++, with zero
> overhead:
>
> #include <array>
>
> void func(const std::array<int,3>& i)
> {
> }
>
> int main() {
> func({ 1,2,3 });
> }

Notice, however, that those values will be copied to a temporary array
(if I understand correctly how std::array works), rather than just
a pointer to a static array being passed to the function.

(Also, if I understand correctly,) this is different from the function
taking an initializer_list, in which case nothing will be copied, and
the pointer inside that object will directly point to that static
array (even if it's "temporary" in scope).

Alf P. Steinbach

unread,
Jun 25, 2018, 9:21:42 AM6/25/18
to
Well that makes a dynamic copy, even it it's likely to be optimized away.

OP: the basic C++ stuff goes like this:

template< int n >
void func( const int (&a)[n] )
{}

...
func({ 1,2,3 });

But I prefer a few named type builders (which is entirely reusable code,
so the definitions would ordinarily not be there, just an `#include`):

#include <limits.h>

using Size = ptrdiff_t;
template< class Type > using ref_ = Type&;
template< Size n, class Item > using array_of_ = Item[n];

template< int n >
void func( ref_<const array_of_<n, int>> a )
{}

...
func({ 1,2,3 });

Since arrays are so special, the declarations

const array_of_<3, int>

and

array_of_<3, const int>

mean the same. The const-ness of an array is a const-ness of its items,
and vice versa. With `std::array` there is a type difference.

#include <array>
#include <limits.h>
#include <iostream>
#include <type_traits>

using Size = ptrdiff_t;
template< class Type > using ref_ = Type&;
template< Size n, class Item > using array_of_ = Item[n];


auto main()
-> int
{
using namespace std;
using A = const array_of_<3, int>;
using B = array_of_<3, const int>;
cout << (is_same_v<A, B>? "Identical" : "Different") << " raw
types.\n";
using C = const array<int, 3>;
using D = array<const int, 3>;
cout << (is_same_v<C, D>? "Identical" : "Different") << "
std::array types.\n";
}

Output:

Identical raw types.
Different std::array types.

So there are some differences between `std::array` and raw arrays,
include the (formal) data copying for `std::array` mentioned above, and
the type conflation for raw arrays shown by the last program.

Cheers!,

- Alf

bol...@cylonhq.com

unread,
Jun 25, 2018, 10:13:32 AM6/25/18
to
On Mon, 25 Jun 2018 14:19:04 +0300
Paavo Helde <myfir...@osa.pri.ee> wrote:
>On 25.06.2018 11:59, bol...@cylonHQ.com wrote:
>> Why is it I can do the following (even in C)...
>>
>> int i[] = { 1,2,3 }
>>
>> but in C++ 2011 (and newer?) I still can't do:
>>
>> void func(int i[])
>> {
>> }
>>
>>
>> func({ 1,2,3 })
>
>This is because void func(int i[]) is the same than void func(int* i)
>(by ancient C rules). How should the system know how to convert some
>random initializer_list into a pointer.

The same way it knows how to do it in the first definition. Why would this be
difficult to implement?

>As you see, the old C syntax is misleading and error-prone. Fortunately
>in C++11 you can rewrite the above in a perfectly fine C++, with zero
>overhead:

Unfortunately the problem with "perfectly fine" c++ is , as in this case, it
takes a C concept and adds a lot of syntactic noise to it plus some CPU
overhead to create the std::array objects that are being referenced.

Given the choice of "int i[]" and "const std::array<int,3>& i" I wouldn't
pick the latter, and if I did need the flexibility of std::array I'd
probably just use a vector anyway.

bol...@cylonhq.com

unread,
Jun 25, 2018, 10:17:25 AM6/25/18
to
On Mon, 25 Jun 2018 15:21:30 +0200
"Alf P. Steinbach" <alf.p.stein...@gmail.com> wrote:
> auto main()
> -> int

Seriously?? Apart from that going against 40 years of convention the whole
point of auto functions is for when you DON'T know the return type in
advance.


Paavo Helde

unread,
Jun 25, 2018, 12:38:11 PM6/25/18
to
On 25.06.2018 17:13, bol...@cylonHQ.com wrote:
> On Mon, 25 Jun 2018 14:19:04 +0300
> Paavo Helde <myfir...@osa.pri.ee> wrote:
>> On 25.06.2018 11:59, bol...@cylonHQ.com wrote:
>>> Why is it I can do the following (even in C)...
>>>
>>> int i[] = { 1,2,3 }
>>>
>>> but in C++ 2011 (and newer?) I still can't do:
>>>
>>> void func(int i[])
>>> {
>>> }
>>>
>>>
>>> func({ 1,2,3 })
>>
>> This is because void func(int i[]) is the same than void func(int* i)
>> (by ancient C rules). How should the system know how to convert some
>> random initializer_list into a pointer.
>
> The same way it knows how to do it in the first definition. Why would this be
> difficult to implement?

The first usage

int i[] = { 1,2,3 };

actually defines an array, not a pointer. Try

int* i = { 1,2,3 };

and it gives the exact same error ("cannot convert from 'initializer
list' to 'int *'").

The fact that the two usage cases look the same but are not are due to
the problems with the old C syntax.

>
>> As you see, the old C syntax is misleading and error-prone. Fortunately
>> in C++11 you can rewrite the above in a perfectly fine C++, with zero
>> overhead:
>
> Unfortunately the problem with "perfectly fine" c++ is , as in this case, it
> takes a C concept and adds a lot of syntactic noise to it plus some CPU
> overhead to create the std::array objects that are being referenced.
>
> Given the choice of "int i[]" and "const std::array<int,3>& i" I wouldn't
> pick the latter, and if I did need the flexibility of std::array I'd
> probably just use a vector anyway.

What you want is a temporary array, and both the end-points are
something else (an initializer list and a pointer). The compiler will
not try to guess what's in your mind, so you need to indicate somehow
you want to have array in-between. If you don't like to do that on the
callee site, you have to do this at the caller site:

void func(const int i[])
{
}

typedef const int(&a)[3];

int main() {
func( a { 1, 2, 3 });
}

(you need to add consts because C++ is trying to keep you from
accidentally modifying the temporary).


bol...@cylonhq.com

unread,
Jun 26, 2018, 5:52:07 AM6/26/18
to
On Mon, 25 Jun 2018 19:37:59 +0300
Paavo Helde <myfir...@osa.pri.ee> wrote:
>On 25.06.2018 17:13, bol...@cylonHQ.com wrote:
>> The same way it knows how to do it in the first definition. Why would this be
>
>> difficult to implement?
>
>The first usage
>
>int i[] = { 1,2,3 };
>
>actually defines an array, not a pointer. Try
>
>int* i = { 1,2,3 };
>
>and it gives the exact same error ("cannot convert from 'initializer
>list' to 'int *'").
>
>The fact that the two usage cases look the same but are not are due to
>the problems with the old C syntax.

You still haven't answered the question as to why my original idea would
be difficult to implement. I can't see any reason why it couldn't be done.

>> pick the latter, and if I did need the flexibility of std::array I'd
>> probably just use a vector anyway.
>
>What you want is a temporary array, and both the end-points are
>something else (an initializer list and a pointer). The compiler will
>not try to guess what's in your mind, so you need to indicate somehow
>you want to have array in-between. If you don't like to do that on the
>callee site, you have to do this at the caller site:
>
>void func(const int i[])
>{
>}
>
>typedef const int(&a)[3];
>
>int main() {
> func( a { 1, 2, 3 });
>}
>
>(you need to add consts because C++ is trying to keep you from
>accidentally modifying the temporary).

That might be what you have to do now, I'm asking why they DIDN'T implement
a much simpler syntax given its already used for lvalue initialisation.

Paavo Helde

unread,
Jun 26, 2018, 8:01:57 AM6/26/18
to
On 26.06.2018 12:51, bol...@cylonHQ.com wrote:
> On Mon, 25 Jun 2018 19:37:59 +0300
> Paavo Helde <myfir...@osa.pri.ee> wrote:
>> On 25.06.2018 17:13, bol...@cylonHQ.com wrote:
>>> The same way it knows how to do it in the first definition. Why would this be
>>
>>> difficult to implement?
>>
>> The first usage
>>
>> int i[] = { 1,2,3 };
>>
>> actually defines an array, not a pointer. Try
>>
>> int* i = { 1,2,3 };
>
> That might be what you have to do now, I'm asking why they DIDN'T implement
> a much simpler syntax given its already used for lvalue initialisation.

As I said, that lvalue was an array, not a pointer. If you write

void func(int x[])

then x is a pointer, which is something entirely different than an array.

So maybe your proposal is to add a special conversion rule: whenever
there is a need to convert an initializer list into a pointer, make a
hidden temporary array instead, initialize it via the initializer list
and assign the pointer to the address of the first element.

Alas, this proposal does not work with the idea that single scalars like
pointers can be also initialized via brace syntax (for "uniform"
initialization and avoiding "most vexing parse" fiasco):

int x = {2};
int y = {}; // initialized to 0
int** z = {nullptr}; // z initialized to NULL by current rules.

The new proposal would make the last z to point to the 1-element array
instead, which would change the existing behavior and the simple scalar
initialization rule. Moreover, the temporary array would be destroyed at
the semicolon, so the pointer would become dangling.

The same holds for function calls:

void foo(int** p);
foo({nullptr}); // currently passes p=NULL.


A bit saner rule would be to add special rules for [] syntax in function
parameters. Currently void foo(int* x) and void foo(int x[]) are the
same, but in principle they could mean different things.

#include<iostream>
void func1(int** b1) {std::cout << b1 << "\n";}
void func2(int* b2[]) {std::cout << b2 << "\n";}

int main() {
int** a1 = {nullptr};
int* a2[] = {nullptr};
std::cout << a1 << "\n" << a2 << "\n";
func1({nullptr});
func2({nullptr});
}

Currently this prints:
0000000000000000
000000000029F8A0
0000000000000000
0000000000000000

because func1 and func2 are identical. The new rule would make them
different so that the last line would also print non-zero.

I guess such a change could be done hypothetically, but it would
effectively change the old C rule of array decay into a pointer. I guess
all C++ specialists are so familiar with that rule that it has never
occurred to them to change it, and probably they are right, changing
something so low-level probably has bad consequences.







Manfred

unread,
Jun 26, 2018, 8:49:36 AM6/26/18
to
On 6/26/2018 2:01 PM, Paavo Helde wrote:
> On 26.06.2018 12:51, bol...@cylonHQ.com wrote:
>> On Mon, 25 Jun 2018 19:37:59 +0300
>> Paavo Helde <myfir...@osa.pri.ee> wrote:
>>> On 25.06.2018 17:13, bol...@cylonHQ.com wrote:
>>>> The same way it knows how to do it in the first definition. Why
>>>> would this be
>>>
>>>> difficult to implement?
>>>
>>> The first usage
>>>
>>> int i[] = { 1,2,3 };
>>>
>>> actually defines an array, not a pointer. Try
>>>
>>> int* i = { 1,2,3 };
>>
>> That might be what you have to do now, I'm asking why they DIDN'T
>> implement
>> a much simpler syntax given its already used for lvalue initialisation.
>
> As I said, that lvalue was an array, not a pointer. If you write
>
> void func(int x[])
>
> then x is a pointer, which is something entirely different than an array.
>
[...]

>
> I guess such a change could be done hypothetically, but it would
> effectively change the old C rule of array decay into a pointer.
The fact is that the C rule is old, but still very valid. This means
that it is a legacy that is practically required to stick to.
On top of that, meanwhile this has become C++ legacy too, and breaking
changes are notoriously on top of the "no go" list.

I guess
> all C++ specialists are so familiar with that rule that it has never
> occurred to them to change it, and probably they are right, changing
> something so low-level probably has bad consequences.
>
I don't think it has never occurred, in fact even amongst C specialists
the array to pointer decay rules have been long and frequently discussed
and objected, but still the need not to break existing code is the
strongest force - which by the way I can understand.

Tim Rentsch

unread,
Jun 30, 2018, 2:01:47 PM6/30/18
to
I found this the most sensible commentary in the thread.

As to the original question, it seems clear that what boltar is
asking for /could/ be done. The more important question is
/should/ it be done? That is a much harder question.

(<editorial>Personally I find how initializer lists are done in
C++ to be kind of half-baked. If we were working in C this
wouldn't even be a question - just write (int[]){ 1, 2, 3 } and
the problem is solved. It seems very strange that initializer
lists in C++ are wired in as special cases at selected points
in the syntax, rather than just being expressions that could
be written whenever needed. Of course I'm sure there are reasons
for why they were done as they were, but the end result is a
bigger patchwork quilt in a syntactic terrain that was already
pretty rocky. For some reason I am reminded of John McCarthy's
quote about various features added to Lisp...</editorial>)

bol...@cylonhq.com

unread,
Jul 2, 2018, 4:46:08 AM7/2/18
to
On Sat, 30 Jun 2018 11:01:34 -0700
Tim Rentsch <t...@alumni.caltech.edu> wrote:
>C++ to be kind of half-baked. If we were working in C this
>wouldn't even be a question - just write (int[]){ 1, 2, 3 } and

I never realised you could do that with C function parameters. Is that C99?
It just shows that one is never too old to learn something new!

Bart

unread,
Jul 2, 2018, 5:34:11 AM7/2/18
to
On 25/06/2018 13:56, Juha Nieminen wrote:
> Paavo Helde <myfir...@osa.pri.ee> wrote:
>> As you see, the old C syntax is misleading and error-prone. Fortunately
>> in C++11 you can rewrite the above in a perfectly fine C++, with zero
>> overhead:
>>
>> #include <array>
>>
>> void func(const std::array<int,3>& i)
>> {
>> }
>>
>> int main() {
>> func({ 1,2,3 });
>> }
>
> Notice, however, that those values will be copied to a temporary array

The OP's example used constant elements but that array to be passed
could also have been {f(),rand(),(int){1,rand(),3}} (the last element
intended to be an array that decays to a pointer that is then cast to int).

> (if I understand correctly how std::array works), rather than just
> a pointer to a static array being passed to the function.
>
> (Also, if I understand correctly,) this is different from the function

It's also different in that the original takes an int[] parameter, that
is an int*, which can be pointer to any number of values, not just 3.


--
bart

Tim Rentsch

unread,
Jul 10, 2018, 9:51:34 AM7/10/18
to
The construction is called a compound literal, and indeed was
added in C99. The syntax for it is

( type-name ) { initializer-list }

(and as usual the initializer-list is allowed an optional
trailing comma). It creates an object of automatic storage
duration in the smallest enclosing block, initialized each time
the compound literal is reached. Oh by the way it isn't just
function arguments, compound literals can be used as regular
expressions, with the same precedence level as function calls
or array indexing.
0 new messages