7

Why I can't desalinize std::array like this?

#include <array>

struct Point
{
    float x;
    float y;
};

int main()
{
   std::array<Point, 3> m_points { 
      { 1.0f, 1.0f }, 
      { 2.0f, 2.0f }, 
      { 3.0f, 3.0f }
   };
}

Doing this I get error:

error: too many initializers for std::array<Point, 3ul>

but it works like this:

std::array<Point, 3> m_points { 
   Point{ 1.0f, 1.0f }, 
   Point{ 2.0f, 2.0f }, 
   Point{ 3.0f, 3.0f } 
};

In contrast std::map can be initialized with both ways written below:

   std::map<int, int> m1 {std::pair<int, int>{1,2}, std::pair<int, int>{3,4}}; 
   std::map<int, int> m2 {{1,2}, {3,4}};
Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
Narek
  • 38,779
  • 79
  • 233
  • 389
  • 2
    Wrong number of braces? Try `std::array m_points {{{ 1.0f, 1.0f }, { 2.0f, 2.0f }, { 3.0f, 3.0f }}};`. [Seems to work...](http://coliru.stacked-crooked.com/a/747be1b02cacb8e2) – ildjarn Sep 05 '16 at 08:13
  • I think the problem is that the compiler does not know what struct you want to initialize in the first example. Even though you're telling the array the type it can not see that you want `Point` – meetaig Sep 05 '16 at 08:15
  • @meetaig looks like that to me too, but same kind of code works for `std::map`, you don't need to write `std::map m {std::pair{1,2}, std::pair{3,4}};` instead you just can write `std::map m {1,2}, 3,4}};` What is the difference? – Narek Sep 05 '16 at 08:18
  • 3
    I have never heard `desalinize` in this context before. I have inferred it to mean initialize? – RedX Sep 05 '16 at 08:20
  • 5
    What is the meaning of _desalinize_ in this context? – moooeeeep Sep 05 '16 at 08:20
  • Well this is me just guessing, but map expects something like a `std::pair`, so maybe they built in something to automatically convert it when no further information is given? Whereas array can take any type so it would be difficult to make something this specialized for the implementation – meetaig Sep 05 '16 at 08:21
  • 3
    @Narek, the c'tor type. `std::array`s are all implicitly declared. `std::map` has a c'tor taking an `std::initializer_list`. – StoryTeller - Unslander Monica Sep 05 '16 at 08:21

2 Answers2

7

In this declaration and initialization

   std::array<Point, 3> m_points { 
      { 1.0f, 1.0f }, 
      { 2.0f, 2.0f }, 
      { 3.0f, 3.0f }
   };

the compiler considers the first initializer in braces like the initializer of the whole array (of the internal aggregate). std::array is an aggregate that contains another aggregate.

Write instead

   std::array<Point, 3> m_points {
      { 
      { 1.0f, 1.0f }, 
      { 2.0f, 2.0f }, 
      { 3.0f, 3.0f }
      }
   };

In the second case

std::array<Point, 3> m_points { 
   Point{ 1.0f, 1.0f }, 
   Point{ 2.0f, 2.0f }, 
   Point{ 3.0f, 3.0f } 
};

each initializer is considered sequentially as an initializer of a next element of the internal aggregate.

Consider this simple demonstrative program.

#include <iostream>

struct array
{
    int a[10];
};

int main()
{
    array a = { { 0, 0 }, { 1, 1 } };

    return 0;
}

The compiler issues an error like

prog.cpp:14:33: error: too many initializers for 'array'
  array a = { { 0, 0 }, { 1, 1 } };
                                 ^

That is it decided that { 0, 0 } is an initializer of the internal array (internal aggregate). Thus the next initializer in braces does not have a corresponding data member in the outer aggregate (structure).

Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
  • I totally don't understand why adding two more curly braces solves the problem. – Narek Sep 05 '16 at 08:26
  • @Narek, because with the extra pair of curly braces (or `=`) you'd be doing aggregate initialization. – StoryTeller - Unslander Monica Sep 05 '16 at 08:30
  • @StoryTeller Maybe and analogical example will clarify? – Narek Sep 05 '16 at 08:31
  • 1
    @Narek Usually std::array is defined like a structure containing an array. The first braces inside the outer braces is considered as an initializer of this array. When there are more than one braces then the compiler decides that there are more initializers than members of the aggregate. – Vlad from Moscow Sep 05 '16 at 08:31
  • Why I have to deal with the internal implementation of the `std::array`? I just want to initialize it and I don't care how it is designed. – Narek Sep 05 '16 at 08:32
  • 1
    @Narek, because the standard defined it to be a light holder of a bare array, thus able to be aggregate initialized. The dup linked above is quite descriptive about this. – StoryTeller - Unslander Monica Sep 05 '16 at 08:34
3

std::array has no explicitly defined constructors, unlike other standard containers such as std::vector or std::map, but only the automatically provided constructors. With std::vector, the compiler will try to match your expression against each available constructor and for a construct like

std::vecor<Point> m_points { {1.0f,1.0f}, {2.0f,2.0f}, {3.0f,3.0f} };

finds a match with the constructor

std::vector::vector(initializer_list<T>, const Allocator& = Allocator() );

But with std::array, it must use aggregate initialisation of the underlying array (Point[3] in your case) and hence your construction does not match. To get it to work, you must add another pair of braces

std::array<Point, 3> m_points { 
  { { 1.0f, 1.0f },
    { 2.0f, 2.0f }, 
    { 3.0f, 3.0f } }
};
Walter
  • 44,150
  • 20
  • 113
  • 196
  • Why I have to deal with the internal implementation of the std::array? I just want to initialize it and I don't care how it is designed. Do you agree that this is shitty? – Narek Sep 05 '16 at 08:36
  • That's another, completely different question altogether. Don't expect answers to questions you didn't (initially) ask. – Walter Sep 05 '16 at 08:37
  • Neither in comments? :) – Narek Sep 05 '16 at 08:42