ISO/ IEC JTC1/SC22/WG21 N0725


Accredited Standards Committee X3       Doc No: X3J16/95-0125   WG21/N0725
Information Processing Systems          Date:    Jun 30, 1995
Operating under the procedures of       Project: Programming Language C++
American National Standards Institute   Ref Doc:
                                        Reply to: Josee Lajoie
                                                  (josee@vnet.ibm.com)
 

Initialization Issues and Proposed Resolutions
==============================================
 
o Initialization
================
 
 *Issue 476:
  ----------
  8.5p6 says:
  "If no initializer is specified for an object with automatic or
   dynamic storage duration, the object and its subobjects, if any,
   have an indeterminate initial value."
 
  The draft doesn't say that referring to an object that has
  indeterminate value results in undefined behavior.
 
  Proposal 476:
  -------------
  Add the following text at the end of 8.5 paragraph 6:
    "Referring to an object with an indeterminate value results in
     undefined behavior."
 
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
 
 *Issue I1:
  ---------
  Tthe first paragraph 8.5.1[dcl.init.aggr] excludes classes with
  nonstatic reference or const members from being aggregates.
  What's wrong with:
 
    struct S { const int member; } object = { 0 };
 
  or
 
    int i;
    struct T { int &r; } t = { i };
 
  ?
 
  Proposal I1:
  ------------
  After the core work on initialization at the Austin meeting,
  paragraph 1 of 8.5.1 [dcl.init.aggr] was:
 
    "An aggregate is an array or a class (_class_) with no
     user-declared constructors (_class.ctor_), no private or protected
     non-static data members (_class.access_), no base classes
     (_class.derived_), and no virtual functions (_class.virtual_)."
 
  I propose that we go back to this wording.
 
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
 
 *Issue Box 15:
  -------------
  3.6.1[basic.start.main] paragraph 1 says:
    "A program shall contain a global function called main, which is
     the designated start of the program."
 
  Must all C++ program defined main?
  In C, a freestanding environment does not require main; the program
  invocation is implementation-defined. Should this be the case in C++?
 
  Proposal Box 15:
  ----------------
  Neal Gafter answers this question in Box 15.
  His answer should be incorporated into the WP as a note.
  And maybe add this as an item in appendix C (C compatibility)
 
  "[Note: Unlike C, a C++ freestanding environment requires that main
    be defined.  This guarantees that the semantics of program start
    and termination (in particular the construction and destruction of
    objects with static storage duration) behaves as defined in this
    International Standard].
 
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
 
 *Issue Box 16:
  -------------
  3.6.1[basic.start.main] paragraph 4 says:
    "Calling the function [exit] declared in <cstdlib> terminates the
     program without leaving the current block and hence without
     destroying any objects with automatic storage duration."
 
  Why is an implementation prohibited from destroying objects with
  automatic storage duration when exit is called?
  Could it say it is implementation-defined?
  Could the standard require that the implementation destroy objects
  with automatic storage duration when exit is called?
 
  Proposal Box 16:
  ----------------
  I believe prohibiting implementations from destroying objects with
  automatic storage duration when exit is called is too limiting.
 
  Replace 3.6.1[basic.start.main] paragraph 4 with:
    "Calling the function [exit] declared in <cstdlib> terminates the
     program leaving the current block and hence destroying the objects
     with automatic storage duration."
 
  This is a bit radical...  however, I choose this option because this
  offers the greatest guarantee for users.
 
  The fall back position is to leave this as implementation-defined,
  in the case some implementations find this requirement unacceptable
  because of performance costs.
 
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
 
 *Issue 462:
  ----------
  What happens if 'exit' is called from a destructor?
  What happens if a destructor throws an exception and the user-defined
  terminate routine calls 'exit'?
 
  Proposal 462:
  -------------
  The draft should say that calling 'exit' from a destructor results in
  undefined behavior.
  Add to 3.6.1 [basic.start.main] paragraph 4:
    "If 'exit' is called during the destruction at block exit of
     objects with automatic storage duration or during the destruction
     at the end of the program of objects with static storage duration,
     the program results in undefined-behavior."
 
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
 
 *Issue I2:
  ---------
  3.6.2[basic.start.init] paragraph 1 says:
    "The initialization of nonlocal objects with static storage duration
     defined in a translation unit shall be done before the first use
     of any function or object defined in that translation unit."
 
  It was suggested that the above wording be clarified as follows:
 
  Proposal I2:
  ------------
    "The initialization of nonlocal objects with static storage duration
     defined in a translation unit shall be done before the first use
     (where a use follows the invocation of 'main' and does not take
     into account any usage within initializations) of any function or
     object defined in that translation unit."
 
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
 
 *Issue 430:
  ----------
  What is the order of destruction of local static variables?
 
  3.6.3[basic.start.term] paragraph 1 says:
    "Destruction is done [for objects of static storage duration] in
     reverse order of initialization."
  6.7[stmt.dcl] says:
    "The destructor is called ... exactly when is unspecified."
 
  Discussion 430:
  ---------------
  The possibilities:
  1) The order of destruction of local variables with static storage
     duration is unspecified. [as 6.7 implies].
  2) It is guaranteed that local variables with static storage duration
     declared in the same function are destroyed in reverse of
     construction.  The order of destruction of local variables with
     static storage duration is otherwise unspecified.
  3) It is guaranteed that variables with static storage duration
     declared in the same translation unit (either declared at block
     scope or declared in namespace scope) are destroyed in reverse of
     construction.  The order of destruction of local variables with
     static storage duration is otherwise unspecified.
  4) It is guaranteed that all variables with static storage
     duration in the program (either declared at block scope or
     declared in namespace scope) are destroyed in reverse of
     construction.  [as 3.6.3 implies].
 
  4) is the nicest option for users.  However, it is the most expensive
  option for implementations.  Mike Ball has commented earlier that
  this option does not work well for dynamic libraries.  However, the
  draft does not describe the behavior of programs using dynamic
  libraries in many other situations.  The committee's position has been
  to leave the description of programs using dynamic libraries as
  implementation-defined.  Maybe here is a situation where this applies
  as well: the order of destruction of local variables with static
  storage duration if the program uses dynamic libraries is
  implementation-defined.
 
  Proposal 430:
  -------------
  Adopt option 4).
  The options seem less and less as the number gets smaller.
 
  3.6.3[basic.start.term] paragraph 1 should say:
    "Destruction of objects with static storage duration declared in
     namespace scope or in block scope is done in reverse order of
     initialization."
 
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
 
 *Issue 484:
  ----------
  When must objects with static storage duration be destroyed wrt to
  calls to functions registered with atexit?
 
  3.6.3[basic.start.term] paragraph 1 says:
    "If atexit is to be called, the implementation shall not destroy
     objects initialized before an atexit call until after the function
     specified in the atexit() call has been called."
 
  6.7[stmt.dcl] paragraph 5 says:
    "The destructor [for local object with static storage duration]
     is called either immediately before or as a part of the calls to
     the atexit functions."
 
  18.3[lib.support.start.term] paragraph 3 says:
    "-- First all functions f registered by calling atexit(f) are called,
        in reverse order of their registration.
     -- Next, all static objects are destroyed in the reverse order of
        their construction. ..."
 
  Discussion 484:
  -------------
  I see two options:
  1) all functions registered with atexit must have completed before
     any destructor for objects with static storage duration (declared
     either at block scope or at namespace scope) is called.
     (in reverse order of construction, as described in issue 430).
  2) Allow what 3.6.3 seems to imply:
    "An implementation shall not destroy objects with static storage
     duration initialized before an atexit call until after the function
     specified in the atexit() call has been called."
 
     This second option allows calls to the destructors for objects
     with static storage duration to preceed calls to functions
     registered with atexit, as long as the construction of the object
     with static storage duration completed after the function was
     registered with atexit.
 
  Proposal 484:
  -------------
  Adopt solution 1.
  I prefer this solution because it gives example B) below a more
  intuitive behavior.
 
  3.6.3[basic.start.term] paragraph 1 should say:
    "If atexit is to be called, the implementation shall not destroy
     objects of static storage duration until after the functions
     specified in the atexit() call have been completed."
 
  6.7[stmt.dcl] paragraph 5 should say:
    "The destruction of local object with static storage duration
     follows the rules described in 3.6.3."
 
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
 
  Examples [from Jerry Schwarz in core-5596]:
 
  A) What is the correct thing to happen if atexit is called during
     construction of a static object?
 
     Proposal:
     With the proposals above, all objects with static storage duration
     are destroyed after the calls to the functions registered with
     atexit have completed.  Since destruction cannot take place as part
     of the calls to the functions registered with atexit, the timing of
     the construction of an object with static storage duration wrt call
     to atexit does not matter.
 
  B) What should happen if a static object is constructed during a call
     to a function that was registered via atexit?
 
     void f() {
       static T t(1);
     }
     void g() {
       static T u(2);
     }
     main() {
       atexit(f);
       atexit(g);
       f();     // ensures 't' is constructed, 'u' is not yet
       exit(0); // now what?  calls 'g' and then 'f'
       // two questions arise:
       // 1) is 'u' destroyed?
       //    (it wasn't constructed prior to the atexit function
       //     calls)
       // 2) has 't' been destroyed prior to calling 'f' again?
     }
 
     Proposal:
     Since objects with static storage duration are destroyed after all
     functions registered with atexit have completed, upon exit of the
     program,
     1) the functions registered with atexit are first executed:
        1) g() [called first]
        2) f()
     2) objects with static storage duration are then destroyed,
        in reverse order of construction:
        1) u [is destroyed first]
        2) t
 
  C) What if exit is called from the initialization of a variable with
     static storage duration before main even begins to run?
 
     Proposal:
     The objects with static storage duration for which construction
     has completed are destroyed in reverse order of construction.
 
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
 
 *Issue 521:
  ----------
  3.6.3[basic.start.term] paragraph 3 says:
    "Calling the function [abort] terminates the program without
     executing destructor for static objects and without calling the
     functions passed to atexit()."
 
  This should be clearer to indicate that the destructors for local
  objects with automatic storage duration are not called either.
 
  Proposal 521:
  -------------
  Change the sentence above to say:
    "Calling the function [abort] terminates the program without
     calling the destructor for objects of automatic or static storage
     duration and without calling the functions registered with
     atexit()."
 
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
 
 *Issue 429:
  ----------
  Is the following initialization of "r" performed "statically" (that
  is, at link time) or "dynamicaly" (that is, at its turn among
  dynamically initialized nonlocal objects in the translation unit)?
 
    extern int x;
    int &r = x;
 
  Proposal 429:
  -------------
  If we want some form of reference initialization to take place
  during the phase of constant initialization, another category of
  "constant expressions" is needed in 5.19[expr.const].
 
  Something like this:
    "A 'reference constant expression' is an lvalue designating an
     object of static storage duration or a function.  The subscripting
     '[]' operator, the class member access '.' and '->' operators, the
     '&' and '*' unary operators, and reference casts (except those
     invoking user-defined conversion functions (12.3) and except
     dynamic_casts (5.2.6) can be used by the lvalue expression of a
     reference constant expression, but the value of an object shall not
     be accessed by the use of these operators.  An lvalue expression
     that designates a member or base class of a non-POD class object
     (9) is not a reference constant expression (12.7).  Function calls
     shall not be used in a reference constant expression, even if the
     function is inline and has a reference return type."
 
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
 
 *Issue 485:
  ----------
  12.4[class.dtor] paragraph 5 says:
    "Bases and members are destroyed in reverse order of their
     construction."
 
  Since construction takes place over time, is the order to be reversed
  the one when the constructor begins or when it finishes.
 
  Proposal 485:
  -------------
  The one when it finishes.
 
  Change 12.4 paragraph 5 to say:
    "Bases and members are destroyed in reverse order of the completion
     of their construction."
 
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
 
 *Issue 359:
  ----------
  12.6.2[class.base.init] describes the order in which the members and
  base class parts of an object of class type are initialized.  It does
  not, however, specify when the expressions used in the
  "mem-initializers" are to be evaluated.
 
  Example:
    struct S {
       int i;
       int j;
       S() : i(0), j(i) {}
    };
 
  12.6.2 paragraph 1 requires that i be initialized before j.  However,
  the rules in 1.8[intro.execution] indicate that, since there is no
  function call (constructor call) to initialize the member i, there is
  no sequence point between the evaluation of the initializer for i and
  the evaluation of the initializer for j.  The current rules therefore
  permit the following order of execution:
    - Fetch the (uninitialized) value of i
    - Initialize i to 0
    - Initialize j to the previously fetched value of i which is
      probably not what the programmer intended.
 
  Discussion 359:
  ---------------
  There are two possible solutions:
  1) Keep the status quo.
  2) Adopt a new rule that would force a sequence point between the
     evaluation of the initializers in the ctor-initializer list.
 
     Here is the new wording for option 2 suggested by Patrick Smith:
     "The expressions in the expression-list part of a mem-initializer
      are evaluated (including all side effects) immediately before the
      corresponding initialization is performed.  When one base class or
      member is initialized before another base class or member, the
      expressions used in the mem-initializer for the second are
      evaluated (including all side effects) after the first
      initialization is performed."
 
  Proposal 359:
  -------------
  Keep the status quo (option 1).
 
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
 
 *Issue Box 46:
  -------------
  Should the standard mandate that const members and reference members
  be initialized in the ctor-initializer of their owning class?
  Example:
    struct B {
      const int i;
      B();
    };
    B() { } // is this ill-formed because the const member 'i' is not
            // initialized by the constructor ctor-initializer list?
 
  Proposal Box 46:
  ----------------
  Since the standard requires that complete objects that are of const
  or reference type be initialized, it seems to make sense that
  constructors and initializer lists for classes with const or reference
  members provide initializers for these members.
 
  Replace 12.6.2[class.base.init] paragraph 3:
    "Note: when a constructor creates an object of class type X, if X
     has a nonstatic data member m that is of const or reference type
     and if the member is neither specified in a mem-initializer nor
     eligible for default-initialization (8.5), then m will have an
     indeterminate value."
  with:
    "The definition for a constructor for a class X with a nonstatic
     data member m of const or reference type shall either specified
     a mem-initializer for m or m shall be of a class type with a
     user-declared default constructor, otherwise the constructor
     definition is ill-formed."
 
  Similarly, the following text should be added to 8.5.1[dcl.init.aggr],
  at the end of paragraph 2:
    "An initializer list for an aggregate X with a nonstatic data
     member m of const or reference type shall either provide an
     initializer for m or m shall be of a class type with a
     user-declared default constructor, otherwise the initializer list
     is ill-formed."
 
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
 
 *Issue I3:
  ---------
  In Message c++std-core-5700, Erwin Unruh writes:
    "It should be described that a destructor calls all destructors
     for direct bases, virtual bases and members."
 
  Proposal I3:
  ------------
  12.4[class.dtor] paragraph 5 should say:
    "A destructor calls all destructors for direct bases, virtual bases
     and members.  Bases and members are destroyed in reverse order of
     construction (see 12.6.2)."
 
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
 
 *Issue 499:
  ----------
  When are temporaries destroyed when created by the left expression of
  the comma operator?
 
  5.18[expr.comma] paragraph 1 says:
    "All side effects of the left expression are performed before the
     evaluation of the right expression."
 
  This seems to contradict the rule that requires temporaries to be
  destroyed at the end of full expressions.
 
  Proposal 499:
  -------------
  Change the sentence above as follows:
    "All side effects of the left expression, except for destruction of
     temporaries (12.2), are performed before the evaluation of the right
     expression."
 
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
 
 *Issue 477:
  ----------
  When can an implementation create temporaries?
 
  12.2[class.temporary] paragraph 2 contains the phrase
    "In some circumstances it might be necessary or convenient for an
     implementation to generate a temporary object.  Precisely when
     such temporaries are introduced is implementation-defined".
 
  This seems extremely vague.  For example, is the implementation
  allowed to introduce a temporary of type Foo in the following example?
 
    class Foo {
    private:
      Foo();
    };
    int main() {
      return 0;
    }
 
  I believe everyone will agree that the implementation is not allowed
  to introduce a temporary of type `Foo' in the example above and not
  allowed to reject the program as ill-formed.  The WP text should be
  clarified to indicate this.
 
  Proposal 477:
  -------------
  Change the sentences above to say:
    "While evaluating an expression, it might be necessary or
     convenient for an implementation to generate temporary objects to
     hold values resulting from the evaluation of the expression's
     subexpressions.  During this evaluation, precisely when such
     temporaries are introduced is unspecified."
 
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
 
 *Issue 516:
  ----------
  What is the lifetime of "helping" temporaries?
 
  12.2[class.temporary] paragraph 4 says:
    "The first context is when an expression appears as an initializer
     for a declarator defining an object.  In that context, the
     temporary that holds the result of the expression shall persist
     until the object's intialization is complete."
 
  All temporaries created in the evaluation of the expression should
  last until the object is initialized.  It should NOT be limited to the
  result temporary.  For example:
    char *a =  string().c_str;
 
  12.2p5 says:
    "The temporary bound to a reference or the temporary containing the
     sub-object that is bound to the reference persists for the
     lifetime of the reference initialized..."
 
  It should be made explicit, whether helping references also last as
  long as the reference.
    char * &ra = string().c_str;
  Does the string temporary last as long as the reference?
  [answer: Yes, because the temporary of type string contains the member
           subobject c_str bound to the reference ra.
 
  Proposal 516:
  -------------
  Change the sentences in 12.2[class.temporary] paragraph 4 to say:
    "The first context is when an expression appears as an initializer
     for a declarator defining an object... In that context, the
     temporaries created while evaluating the initializer expression
     shall persist until the object's intialization is complete."
 
  Change the sentences in 12.2[class.temporary] paragraph 5 to say:
    "The temporary bound to a reference or the temporary containing the
     subobject that is bound to the reference persists for the lifetime
     of the reference initialized or until the end of the scope in which
     the temporary is created, which ever comes first.  The other
     temporaries created while evaluating the initializer expression for
     the reference are destroyed at the end of the the initializer's
     full expression."
 
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
 
 *Issue 509:
  ----------
  How does "destroy in reverse order of creation" work for temporaries
  bound to references vs temporaries destroyed at the end of the
  initialization?
 
  12.2[class.temporary] paragraph 6:
    "In all cases, temporaries are destroyed in reverse order of
     creation."
 
  This is not quite right.
  Temporaries are destroyed in reverse order of creation, except those
  temporaries created in an expression used to initialize a reference;
  these are destroyed when the reference is destroyed, in reverse order
  of creation.
 
  Proposal 509:
  -------------
  Add at the end of paragraph 4:
    "In that context, the temporaries created while evaluating the
     initializer expression shall persist until the object's
     initialization is complete.  These temporaries shall be destroyed
     in reverse order of creation."
 
  Add at the end of paragraph 5:
    "In each of these situations, the temporaries shall be destroyed in
     reverse order creation."
 
  Delete paragraph 6.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
 
  After all these changes to 12.2[class.temporary]
  paragraph 4 reads:
    "The first context is when an expression appears as an initializer
     for a declarator defining an object.  The object is initialized
     from a copy of the temporary holding the result value of the
     initializer expression.  During this copying, an implementation can
     call the copy constructor many times.  In that context, the
     temporaries created while evaluating the initializer expression
     shall persist until the object's initialization is complete.  These
     temporaries shall be destroyed in reverse order of creation."
  An example is needed to make things clearer.
 
 
  paragraph 5 reads:
    "The temporary bound to a reference or the temporary containing the
     subobject that is bound to the reference persists for the lifetime
     of the reference initialized or until the end of the scope in which
     the temporary is created, which ever comes first; the other
     temporaries created while evaluating the initializer expression for
     the reference are destroyed when the initialization is complete,
     in reverse order of creation.  A temporary holding the result of
     an initializer expression for a declaration that declares a
     reference persists until the end of the scope in which the
     reference declaration occurs.  A temporary bound to a reference in
     a constructor's ctor-initializer (12.6.2) persists until the
     constructor exits.  A temporary bound to a reference parameter in a
     function call (5.2.2) persists until the completion of the complete
     expression containing the call.  A temporary bound in a function
     return statement (6.6.3) persists until the function exits.  In
     each of these situations, the temporaries shall be destroyed in
     reverse order creation."
  An example is needed to make things clearer.