array initialiser list, order of initialisation

Discussion in 'C++' started by kwikius, Jul 10, 2008.

  1. kwikius

    kwikius Guest

    Hi,

    In the following code, the initialisation of the array elements depends on
    the first in the initialiser list being initialised before the second and so
    on. Can I rely on that?


    #include <iostream>

    int main()
    {
    int ar[3] = {
    1,
    ar[0]+1,
    ar[1]+1
    };

    std::cout
    << ar[0] << ' '
    << ar[1] << ' '
    << ar[2] << '\n';

    }

    regards
    Andy Little
     
    kwikius, Jul 10, 2008
    #1
    1. Advertisements

  2. kwikius

    Ivan Novick Guest

    I don't see anything in the standard in section 8.5.1 about
    initializing aggregates, that indicates the order of the initilization
    is guaranteed by the order of the items in the initializer list.
    Since its not explicitly called out in the standard, I guess that
    means it may or may not work on any given platform. Basically as Alf
    said, forget about this syntax.

    Ivan Novick
    http://www.mycppquiz.com/
     
    Ivan Novick, Jul 11, 2008
    #2
    1. Advertisements

  3. kwikius

    joseph cook Guest

    Prefer putting this in your toolbox:
    template<typename T>
    class RampGen
    {
    public:
    RampGen(T init=0, T inc=1) : m_data(init), m_incrementor(inc) {}
    T operator()() { T result = m_data; m_data += m_incrementor; return
    result; }
    T m_incrementor;
    T m_data;
    };

    and then just use:
    std::vector<int> vec(3);
    std::generate(vec.begin(),vec.end(),RampGen(1));

    Joe Cook
     
    joseph cook, Jul 11, 2008
    #3
  4. kwikius

    kwikius Guest

    hmm... thats a shame, cos I like it! :)

    regards
    Andy Little
     
    kwikius, Jul 11, 2008
    #4
  5. kwikius

    James Kanze Guest

    There are actually two separate issues to be considered. First,
    I'm pretty sure that there is a statement in the standard which
    guarantees that arrays are initialized "in order" (and
    destructed in the reverse order). So he's probably safe in that
    regard. The second point is, of course, is there a sequence
    point between the evaluation of the initialization expressions;
    I think each counts as a "complete expression", and the answer
    is yes, but I'm not sure.
    I certainly agree there. As Alf said, if you have to ask...
     
    James Kanze, Jul 11, 2008
    #5
  6. kwikius

    kwikius Guest

    FWIW I think its valid.

    The useage is for a graph showing the piecewise integral of a function of
    some input.

    Anway .. I like it and from what you say I have a strong hunch now that its
    not a problem.

    regards
    Andsy Little
     
    kwikius, Jul 11, 2008
    #6
  7. kwikius

    Jerry Coffin Guest

    As far as I can see, no. IF this used dynamic initialization, you'd be
    guaranteed that the objects in the array would be initialized in order,
    and you'd be guaranteed that there was a sequence point between each
    initialization and the next, so you'd get defined results.

    What you have, however, is an array of items (ints) with no user-
    declared constructors, no private or protected non-static members, no
    base classes and no virtual functions. That means what you have is an
    aggregate, which is static initialized.

    The only guarantee made about order of static initialization is that it
    happens before dynamic initialization. As such, your code currently has
    undefined behavior -- it could work for some compilers, and fail for
    others, or change behavior based on the compilation flags you use, or
    whatever.

    It is, however, trivial to make it work correctly: instead of int's,
    create an array of proxy objects that act like ints:

    class Int {
    int value;
    public:
    Int(int v) : value(v) {}
    operator int &() { return value; }
    };

    Int ar[3] = {
    1,
    ar[0] + 1,
    ar[1] + 1
    };

    Since this has a user-declared ctor, it's not an aggregate. Since it's
    not an aggregate, you get dynamic initialization instead of static
    initialization. Dynamic initialization guarantees that the
    initialization happens in order, with a sequence point between each
    initialization and the next. IOW, it works.

    As far as speed goes, I'm reasonably certain any decent compiler will
    produce identical code for manipulation thsese objects as it would for
    raw ints. For example, given code like:

    int total = 0;

    for (int i=0; i<3; i++)
    total += ar;

    There is no difference between the code produced if ar is an array of
    int or of Int. Of course, that theoretically applies only to the
    compilers I tested -- in theory, some other compiler _could_ produce
    different code, though I'd be rather surprised to see it. Offhand, I
    can't really think of what it _could_ reasonably change...
     
    Jerry Coffin, Jul 11, 2008
    #7
  8. kwikius

    James Kanze Guest

    Could you remind me where this is specified. I was looking for
    it, but I couldn't find it.

    More generally, I'm pretty sure that if you write something
    like:

    void
    f()
    {
    T arr[ 5 ] ;
    }

    it is guaranteed that the constructors of arr are called "in
    order", and that if one exits with an exception, the destructors
    of the already constructed objects are called in the reverse
    order, but I can't find this either.
    What he has is a variable with automatic lifetime, which can't
    have static initialization. Even at namespace scope, it would
    have static initialization, because of the array accesses. All
    of the three compilers I have access to agree, and use dynamic
    initialization.

    (Whether static initialization or dynamic is involved can easily
    be tested with something like:

    int f() ;

    int const i = f() ;
    int const arr[ 3 ] = { 0, arr[0]+1, arr[1]+1 } ;

    int
    f()
    {
    return arr[2] ;
    }

    The initialization of i is dynamic. If the initialization of
    arr is static, it takes place before the call to f() in the
    initialization of i, and i is initialized with 2. If it is
    dynamic, it takes place after, and i is initialized with 0.)
    The requirements for static initialization have been carefully
    formulated so that the initialization value can be determined by
    the compiler. Static initialization requires all of the
    initializers to be constants, and according to the current
    standard, "An integral constant-expression can involve only
    literals, enumerators, const variables or static data member of
    integral or enumeration types initialized with constant
    expressiosn, non-type template parameters of integral or
    enumeration types, and sizeof expressions." Other types of
    constant expressions are even more limited. In no case can a
    constant expression contain an array access (which involves an
    object of array type, which of course isn't an integral type).
    I'm not sure that it's undefined behavior, although it's really
    not very clear. There's clearly a sequence point between the
    evaluation of each initializer expression, since each is a
    complete expression. However, the initialization itself isn't
    part of the expression, so that's not really sufficient.

    It's an interesting question, actually. Suppose a type T, with
    a constructor which takes an int, and something like the
    following:

    int
    f( int i )
    {
    std::cout << i << std::endl ;
    return i ;
    }

    main()
    {
    T arr[ 3 ] = { f( 1 ), f( 2 ), f( 3 ) } ;
    }

    As I said above, I'm pretty sure that the constructors must be
    called in the order arr[0], arr[1], arr[2]. And f(1), must be
    called, and all of its side effects must occur, before f(2) is
    called. But I don't think that there's anything which
    prevents an order like f(1), f(2), f(3), ctor(arr[0]),
    ctor(arr[1]), ctor(arr[2]) (which could make a difference if f
    had side effects, and one of the constructors threw).
    He's got dynamic initialization already, so any guarantees which
    apply to dynamic initialization apply here. But I'm not sure
    that the behavior is defined with dynamic initialization,
    either. (And as I say, although I'm sure they exists, I can't
    find the guarantees concerning the order of array elements in
    dynamic initialization. And the exact wording matters in this
    case.)
     
    James Kanze, Jul 11, 2008
    #8
  9. Actually, the issue is not so much the order of element initialization as it
    is the order in which the initializers themselves are evaluated.

    If I write

    int a[2] = { f(), g() };

    is the implementation permitted to evaluate f(), then evaluate g(), then
    store the result of f() in a[0], and finally store the result of g() in
    a[1]? I don't see any reason why not.
     
    Andrew Koenig, Jul 11, 2008
    #9
  10. kwikius

    Jerry Coffin Guest

    [ ... order of dynamic initialization ]
    3.6.2/1, at least by my reading. What it says could be read as not
    really applying in this case though. It says: "Objects with static
    storage duration defined in namespace scope in the same translation
    unit and dynamically initialized shall be initialized in the order in
    which their definition appears in the translation unit."

    As you pointed out, however, that doesn't really apply in this case,
    because in his code it's a local object, that doesn't have static
    storage duration. Given that it's an automatic variable, the relevant
    language (to the extent that it exists at all) is in 6.7/4: "Otherwise
    such an object is initialized the first time control passes through its
    declaration; such an object is considered initialized upon the
    completion of its initialization."

    In neither case does it explicitly specify the order of initialization
    of element in an array. I'd take the "order of definition" of elements
    in an array as being their order in the array, but I'll admit that's
    never really stated directly.
    This hinges on the same language above. All that's added is the
    destructors being called in reverse order, which is specified in 6.6/2:
    "On exit from a scope (however accomplished), destructors (12.4) are
    called for all constructed objects with automatic storage duration
    (3.7.2) (named objects or temporaries) that are declared in that scope,
    in the reverse order of their declaration."

    As such, if elements in an array are considered defined in their order
    within the array, it does. Otherwise it doesn't. If there were such
    language, it should be somewhere in 8.3.4, but I can't find such a
    thing.

    [ ... ]
    Quite true.
     
    Jerry Coffin, Jul 11, 2008
    #10
  11. kwikius

    James Kanze Guest

    Because they're both complete expressions, and so there are
    sequence points involved. But I'll admit that I don't know
    quite what they are ordering.
     
    James Kanze, Jul 11, 2008
    #11
  12. kwikius

    kwikius Guest

    Thinking about it, the main reason I can think of for leaving the order of
    initialisation open might be to allow parallel initialisation, e.g. loading
    the array into a vector register, barring that it would seem to make no
    sense to leave a temporary somewhere when stack storage has been allocated.
    OTOH As it stands AIUI paralleism is not really viable at the micro level,
    due to shared memory single register hardware model and of course C++ is
    very much tied to sequential model of execution, but I can sort of visualise
    an architecture where an array can be stored in a vector register in one
    cycle as I believe happens as a matter of course in Graphics coprocessors
    etc. Quite how C++ fits in to that type of model I don't know though ...

    regards
    Andy Little
     
    kwikius, Jul 11, 2008
    #12
    1. Advertisements

Ask a Question

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

You'll need to choose a username for the site, which only take a couple of moments (here). After that, you can post your question and our members will help you out.