Initializing std::array

Discussion in 'C++' started by Juha Nieminen, Sep 11, 2012.

  1. How to properly initialize a std::array<std::array<float, 2>, 2> using
    an initializer list?
     
    Juha Nieminen, Sep 11, 2012
    #1
    1. Advertising

  2. Juha Nieminen

    Casey Carter Guest

    On 2012-09-11 11:03, Juha Nieminen wrote:
    > How to properly initialize a std::array<std::array<float, 2>, 2> using
    > an initializer list?
    >


    According to the standard (23.3.2.1/2): An array is an aggregate (8.5.1)
    that can be initialized with the syntax

    array<T, N> a = { initializer-list };

    where initializer-list is a comma-separated list of up to N elements
    whose types are convertible to T.

    In practice, implementations of array<T, N> contain a single data
    member: a C-style array of N Ts. The initialization specified above only
    works because of rules allowing for elision of some curly braces in
    initializers. Some compilers will warn about missing braces.

    The fully-braced form of initializer would then be

    array<T, N> a = {{ initializer-list }};

    Which, despite being non-standard, is in fact _more_ correct for all
    reasonable implementations of std::array. It will also work correctly in
    the presence of nesting:

    array<array<float, 2>, 2> foo = {{ {{ 1.0, 2.0 }}, {{ 3.0, 4.0 }} }};
     
    Casey Carter, Sep 11, 2012
    #2
    1. Advertising

  3. Casey Carter <> wrote:
    > According to the standard (23.3.2.1/2): An array is an aggregate (8.5.1)
    > that can be initialized with the syntax
    >
    > array<T, N> a = { initializer-list };


    If that's the case, then neither gcc nor clang implement it properly
    because both give a warnings when I try it, rather than just working
    as intended.

    > The fully-braced form of initializer would then be
    >
    > array<T, N> a = {{ initializer-list }};
    >
    > Which, despite being non-standard


    So what would be the standard way?
     
    Juha Nieminen, Sep 11, 2012
    #3
  4. Juha Nieminen

    Casey Carter Guest

    On 2012-09-11 11:45, Juha Nieminen wrote:
    > Casey Carter <> wrote:
    >> According to the standard (23.3.2.1/2): An array is an aggregate (8.5.1)
    >> that can be initialized with the syntax
    >>
    >> array<T, N> a = { initializer-list };

    >
    > If that's the case, then neither gcc nor clang implement it properly
    > because both give a warnings when I try it, rather than just working
    > as intended.
    >
    >> The fully-braced form of initializer would then be
    >>
    >> array<T, N> a = {{ initializer-list }};
    >>
    >> Which, despite being non-standard

    >
    > So what would be the standard way?


    The single-braced version is blessed by the standard, presumably because
    it looks like

    T a[N] = { initializer-list };

    and makes it easy to directly replace "T a[N]" with "array<T, N> a" in
    typical code. GCC and clang both implement it correctly, they just
    happen to also emit a very annoying diagnostic.

    Considering how that initialization style depends on dropping braces,
    there's no way it can possibly work in the presence of nested arrays.
     
    Casey Carter, Sep 11, 2012
    #4
  5. Casey Carter <> wrote:
    > Considering how that initialization style depends on dropping braces,
    > there's no way it can possibly work in the presence of nested arrays.


    I think that's the most relevant point relating to my original question.
    If I understand correctly, this should work according to the C++ standard:

    std::array<std::array<int, 2>, 2> table = { { 1, 2 }, { 3, 4 } };

    However, both gcc and clang give an outright error.

    Now I wonder why neither has implemented it properly. Initializing an
    array from an initializer list is quite a trivial thing to do, requiring
    just a few lines of code.

    (Although there's one problem with that: std::initializer_list::size()
    is defined as const, not constexpr. This ought to mean that it's not
    possible to detect at compile time if there are too many elements in
    the initializer list, eg. with a static_assert... I suppose the compilers
    would have to do some trickery about that.)
     
    Juha Nieminen, Sep 12, 2012
    #5
  6. Juha Nieminen

    Nobody Guest

    On Tue, 11 Sep 2012 16:45:01 +0000, Juha Nieminen wrote:

    >> According to the standard (23.3.2.1/2): An array is an aggregate (8.5.1)
    >> that can be initialized with the syntax
    >>
    >> array<T, N> a = { initializer-list };

    >
    > If that's the case, then neither gcc nor clang implement it properly
    > because both give a warnings when I try it,


    The implementation is allowed to emit warnings about anything it likes,
    including perfectly valid code.

    The standard only requires the implementation to accept the above syntax;
    it doesn't require it to do so quietly.
     
    Nobody, Sep 12, 2012
    #6
  7. Nobody <> wrote:
    > On Tue, 11 Sep 2012 16:45:01 +0000, Juha Nieminen wrote:
    >
    >>> According to the standard (23.3.2.1/2): An array is an aggregate (8.5.1)
    >>> that can be initialized with the syntax
    >>>
    >>> array<T, N> a = { initializer-list };

    >>
    >> If that's the case, then neither gcc nor clang implement it properly
    >> because both give a warnings when I try it,

    >
    > The implementation is allowed to emit warnings about anything it likes,
    > including perfectly valid code.
    >
    > The standard only requires the implementation to accept the above syntax;
    > it doesn't require it to do so quietly.


    But as noted, they do not accept things like

    std::array<std::array<int, 2>, 2> a = { { 1, 2 }, { 3, 4 } };

    even though they should.
     
    Juha Nieminen, Sep 12, 2012
    #7
  8. Juha Nieminen

    Nobody Guest

    On Wed, 12 Sep 2012 19:43:57 +0000, Juha Nieminen wrote:

    > But as noted, they do not accept things like
    >
    > std::array<std::array<int, 2>, 2> a = { { 1, 2 }, { 3, 4 } };
    >
    > even though they should.


    You can't omit that particular set of braces.

    A std::array<T,N> is typically a structure containing a single data
    member, an N-element array. On that basis, the initialiser list for a
    std::array<T,N> should have the same structure as the initialiser list as
    a 1xN C array, i.e.:

    std::array<int, 2> a = { { 1, 2 } };

    One set of brackets can be omitted, allowing:

    std::array<int, 2> a = { 1, 2 };

    In the 2D case, the initialiser list for a std::array<std::array<T,N>,M>
    should have the same structure as that for a 1xMx1xN C array, i.e.:

    // compiles without warning
    std::array<std::array<int, 2>, 2> c = { { { { 1, 2 } }, { { 3, 4 } } } };

    You can use either of:

    // compiles, with warnings
    std::array<std::array<int, 2>, 2> d = { { { 1, 2 }, { 3, 4 } } };
    std::array<std::array<int, 2>, 2> e = { 1, 2, 3, 4 };

    but not:

    // does not compile
    std::array<std::array<int, 2>, 2> g = { { 1, 2 }, { 3, 4 } };

    because that would try to use {1,2} to initialise the first (and only)
    data member of the outer array then {3,4} to initialise the second data
    member, which doesn't exist.

    However, while this implementation is typical, it isn't required. AFAICT,
    the standard only requires that the implementation is such that a
    one-dimensional array of scalars can be initialised like a C array. E.g.
    the implementation could actually have the underlying array nested within
    multiple levels of single-element structures.

    The rules on brace elision mean that the 1D case can be initialised like a
    1D C array, but the initialiser list for a nested array requires braces
    which match the implementation.

    It isn't possible to specify the syntax for higher dimensions without
    either specifying the implementation or changing the base language syntax.
     
    Nobody, Sep 13, 2012
    #8
  9. Nobody <> wrote:
    > It isn't possible to specify the syntax for higher dimensions without
    > either specifying the implementation or changing the base language syntax.


    But std::vector<std::vector<int>> v = { { 1, 2 }, { 3, 4 } }; works just
    fine. What's so different about std::array?
     
    Juha Nieminen, Sep 13, 2012
    #9
  10. Juha Nieminen

    Nobody Guest

    On Thu, 13 Sep 2012 10:49:20 +0000, Juha Nieminen wrote:

    > Nobody <> wrote:
    >> It isn't possible to specify the syntax for higher dimensions without
    >> either specifying the implementation or changing the base language syntax.

    >
    > But std::vector<std::vector<int>> v = { { 1, 2 }, { 3, 4 } }; works just
    > fine.


    Only for C++11. Previously, you couldn't use any form of initialiser list.

    > What's so different about std::array?


    std::array doesn't have a constructor, so it can only be initialised by
    aggregate initialisation.
     
    Nobody, Sep 13, 2012
    #10
  11. Juha Nieminen

    Casey Carter Guest

    On 2012-09-13 05:49, Juha Nieminen wrote:
    > Nobody <> wrote:
    >> It isn't possible to specify the syntax for higher dimensions without
    >> either specifying the implementation or changing the base language syntax.

    >
    > But std::vector<std::vector<int>> v = { { 1, 2 }, { 3, 4 } }; works just
    > fine. What's so different about std::array?


    The initialization semantics of std::array are exactly as specified in
    (23.3.2.1/2):

    An array is an aggregate (8.5.1) that can be initialized with the syntax

    array<T, N> a = { initializer-list };

    where initializer-list is a comma-separated list of up to N elements
    whose types are convertible to T.

    That doesn't mean std::initializer_list and it's not even the same word
    "initializer-list" as used elsewhere in the standard, since they define
    it here as "a comma-separated list of up to N elements whose types are
    convertible to T." I don't think that "{1, 2}" in your expression _has_
    a type (at best it would be std::initializer_list<int>) but it certainly
    doesn't have a type convertible to T.

    AFAICT the only compliant and portable way to initialize nested arrays
    is with e.g.:

    std::array<std::array<int, 2>, 2> = {
    std::array<int, 2>{ 1, 2 },
    std::array<int, 2>{ 3, 4 }
    };

    which will give a small raft of compiler warnings.

    If this all seems ridiculous to you, you are not alone. The standard
    does some contortions to satisfy the requirement that std::array be an
    aggregate, so that std::array can fit nearly all use cases for C-style
    arrays.
     
    Casey Carter, Sep 13, 2012
    #11
  12. Nobody <> wrote:
    > On Thu, 13 Sep 2012 10:49:20 +0000, Juha Nieminen wrote:
    >
    >> Nobody <> wrote:
    >>> It isn't possible to specify the syntax for higher dimensions without
    >>> either specifying the implementation or changing the base language syntax.

    >>
    >> But std::vector<std::vector<int>> v = { { 1, 2 }, { 3, 4 } }; works just
    >> fine.

    >
    > Only for C++11. Previously, you couldn't use any form of initialiser list.


    *sigh*

    We are talking about std::array here. Duh.
     
    Juha Nieminen, Sep 14, 2012
    #12
  13. Casey Carter <> wrote:
    > If this all seems ridiculous to you, you are not alone. The standard
    > does some contortions to satisfy the requirement that std::array be an
    > aggregate, so that std::array can fit nearly all use cases for C-style
    > arrays.


    "Nearly" meaning "with the exception of multidimensional arrays, which
    cannot be initialized in the same way as multidimensional C-style arrays"?

    I'm still not quite sure what would be the problem in std::array supporting
    std::initializer_list, except perhaps for the small annoyance that there
    doesn't seem to be a way to get the size of the initializer list at
    compile time in order to give an error if too many elements are specified.

    (Since std::initializer_list is not, AFAIK, a dynamic data container, is
    there a technical reason why its size() function cannot be constexpr?)
     
    Juha Nieminen, Sep 14, 2012
    #13
  14. Juha Nieminen

    ptyxs Guest

    Le 13/09/2012 20:27, Casey Carter a écrit :
    >
    > An array is an aggregate (8.5.1) that can be initialized with the syntax
    >
    > array<T, N> a = { initializer-list };
    >
    > where initializer-list is a comma-separated list of up to N elements
    > whose types are convertible to T.
    >


    Note that possible initialization examples are :

    array<int,3> { 3,45,76 }; // (no '=' signe, probably better style

    and :

    array<int,3> { { 3,45,76 } };
     
    ptyxs, Sep 14, 2012
    #14
  15. Juha Nieminen

    Öö Tiib Guest

    On Friday, September 14, 2012 9:59:12 AM UTC+3, Juha Nieminen wrote:
    > (Since std::initializer_list is not, AFAIK, a dynamic data container, is
    > there a technical reason why its size() function cannot be constexpr?)


    Probably one of the big players screwed it in to make that as "extension"
    for their compiler and achieve that std::initializer_list's usage and
    provided guarantees (that enable optimizations) would differ per
    platform.
     
    Öö Tiib, Sep 14, 2012
    #15
  16. Juha Nieminen

    Casey Carter Guest

    On 2012-09-14 01:59, Juha Nieminen wrote:
    > Casey Carter <> wrote:
    >> If this all seems ridiculous to you, you are not alone. The standard
    >> does some contortions to satisfy the requirement that std::array be an
    >> aggregate, so that std::array can fit nearly all use cases for C-style
    >> arrays.

    >
    > "Nearly" meaning "with the exception of multidimensional arrays, which
    > cannot be initialized in the same way as multidimensional C-style arrays"?


    The other big incompatibility is arrays whose size is determined by the
    number of initializers:

    int foo[] = { 1, 2, 3, 4, 5 };

    for which std::array has no equivalent.

    > I'm still not quite sure what would be the problem in std::array supporting
    > std::initializer_list, except perhaps for the small annoyance that there
    > doesn't seem to be a way to get the size of the initializer list at
    > compile time in order to give an error if too many elements are specified.


    Being an aggregate per 8.5.1 implies no constructors, which rules out
    construction from std::initializer_list.

    > (Since std::initializer_list is not, AFAIK, a dynamic data container, is
    > there a technical reason why its size() function cannot be constexpr?)


    No problem at all; the standard library that comes with GCC 4.7
    implements all of std::initializer_list as constexpr. There are many
    things in the library that could be constexpr but are not required to
    be, I imagine because constexpr was devised somewhat late in the
    standardization process and didn't have time to percolate through all of
    the library requirements.

    What _is_ problematic is writing a constructor that takes a
    std::initializer_list for a std::array equivalent, while accounting for:
    (a) the initializer_list might not have exactly N elements
    (b) T might not be default constructable
    (c) T might not be copy constructable
    (d) T might have a throwing copy constructor
    The only way to construct a C-style array - i.e., without requiring
    default initialization and/or copying - is with a good-old fashioned
    braced aggregate initializer.
     
    Casey Carter, Sep 14, 2012
    #16
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.

Share This Page