ISO/ IEC JTC1/SC22/WG21 N0806

 
Accredited Standards Committee X3       Doc No: X3J16/95-0206   WG21/N0806
Information Processing Systems          Date:   November 3, 1995
Operating under the procedures of       Project: Programming Language C++
American National Standards Institute   Ref Doc:
                                        Reply to: Josee Lajoie
                                                  (josee@vnet.ibm.com)
 
Special Member Functions Issues and Proposed Resolutions
========================================================
 
22 - Must implementations respect access restrictions?
 
  Are access restrictions only limitations on what the programmer may
  write?  Must implementations also respect them for the compiler
  generated code?  (for example, for uses of copy constructors when
  exceptions are thrown, for uses of destructors at the end of a block
  or at the end of the program, ...).
 
  And if implementations must respect access restriction, when must it
  report the errors?
 
    struct B {
    private:
        ~B () { }
    };
 
    struct D : public B {
       ~D () { }  // is the mere existence of D::~D considered an
                  // implicit call of B::~B and therefore a protection
                  // violation?
    };
 
    void f() {
       D d;       // Or is this an error?
     }            // Or is the error when d is destroyed?
 
  Where is it well-formed to implicit use private and protected special
  member functions?  For example, can a private copy constructor be used
  to throw an exception from one of the class member functions?
 
    class A {
      A(const A&);   // copy constructor private
    public:
      void mf();
    };
 
    void A::mf() {
      try {
        throw A();   // Is this valid?
      }
      catch (A a) {  // Is this valid?
      }
    }
 
  Another example:
    class A {
      A();            // default constructor private
    public:
      void mf();
    };
 
    void A::mf() {
      A* pa = new A;  // Is this valid?
    }
 
  Proposed Resolution:
    A program is ill-formed if a special member function is used and is
    not accessible.
 
    The error is detected when the compiler detects an implicit use of
    the special member function (i.e. implicit uses of special member
    functions are listed in issues 556b and 427b).  In the first example
    above, the error is detected when the destructor for ~D is defined.
 
    Access checking is applied as if the special member function was
    used directly in the scope from which the implicit use takes place,
    i.e.
    o in the first example, it is as if ~B was used in the scope of ~D;
      access checking takes place from the scope of class D,
    o in the second example, it is as if the copy constructor was
      explicit used in the throw expression and in the catch block;
      access checking takes place from the scope of class A,
    o in the third example, it is as if the default constructor was
      explicit used in the new expression; access checking takes place
      from the scope of class A.
 
575 - Can programs explicitly name implicitly-declared special member
      functions?
 
  12[special] paragraph 1 says:
    "[Note: ...  Often such special member functions are called
      implicitly.  The implementation will implicitly declare these
      member functions for a class type when the programmer does not
      explicitly declare them.]"
 
    This text should make it clear the implementation won't implicitly
    define a user-declared special member function and that such a
    member function must be defined in the program.
 
    Also, this text should make it clear that implicitly-declared
    special member functions can only be used implicitly by the
    implementation.  User code cannot refer to these special member
    functions if the member functions are not declared and defined in
    the program.
 
  Proposed Resolution:
    I would like to make the text above normative, and add some
    clarifications:
      "A user-declared special member function that is used shall be
       explicitly defined in a program, otherwise the program has
       undefined behavior.  The implementation will implicitly declare
       the special member functions for a class type if the program
       does not explicitly declare them.  Such implicitly-declared
       special member functions can only be used implicitly and shall
       not be defined or referred to explicitly in the program.  For
       example,
 
         struct B {
             // B() implicitly declared
         };
         B::B() { } // ill-formed
      "
 
379 - Invoking member functions which are "not inherited".
 
  Section 5.1/8 of the 1/93 working paper says:
    "A nested-class-specifier (9.1) followed by :: and the name of a
     member of that class (9.2) or a member of a base of that class
     (10) is a qualified-id; its type is the type of the member.  The
     result is the member."
 
  12.1[class.ctor] paragraph 3 says:
    "Constructors are not inherited."
 
  12.4[class.dtor] paragraph 6
    "Destructors are not inherited."
 
  12.8[class.copy] paragraph 5
    "Copy constructors are not inherited."
 
  May a member of a given base class type which is "not inherited" by
  another class type (derived from the given base class type) be invoked
  for an object whose static type is the derived class type if the
  invocation is done using the class-qualified name syntax?  If, not, is
  an implementation obliged to issue a compile-time diagnostic for such
  usage?
 
  Is the behavior "well defined" if an attempt is made to invoke a
  non-inherited member for an object whose static type is that of the
  base class but whose dynamic type is that of the derived class?
 
      struct B {
          virtual ~B () { }
      };
 
      struct D : public B {
          ~D () { }
      };
 
      D D_object;
      D D_object2;
      B *B_ptr = &D_object2;
 
      void caller ()
      {
          D_object.B::~B();               // ok?
          B_ptr->~B();                    // ok?
      }
 
  Proposed Resolution:
    It is ill-formed to call a base class special member function
    (constructor/destructor) using a class member access expression
    (5.2.4) if the special member function is "not inherited" and if the
    object-expression of the class member access is of a class type
    derived from the special member function's class (this, whether or
    not the invocation is done using a qualified name).  i.e.
 
      D_object.~B();         // ill-formed
      D_object.B::~B();      // ill-formed
 
    A program has undefined behavior if a special member function is
    called on an object and the object's type (dynamic type) is not of
    the special member function's class type.  i.e.
 
      B_ptr = &D_object;
      B_ptr->~B();           // undefined behavior
 
284 - Can access declarations apply to special member functions that are
      not inherited?
 
  Proposed Resolution:
    11.2 [class.access.base] should be explicit and say that the access
    restrictions described in this subclause only apply to inherited
    members.
 
    11.3 [class.access.dcl] should say that access declarations cannot
    apply to special member functions that are not inherited.
 
576 - How do volatile semantics affect the ctor and dtor code?
 
  12.1[class.ctor] paragraph 2 says:
    "A constructor can be invoked for a const, volatile or const
     volatile object. FN)
     FN) volatile semantics might or might not be used."
 
  12.4[class.dtor] paragraph 1 says:
    "A destructor can be invoked for a const, volatile or const
     volatile object. FN)
     FN) volatile semantics might or might not be used."
 
  I thought that during the object model discussions, we agreed that
  const and volatile attributes only came into effect at the end of
  constructor and their effect ended at the beginning of destructor.
 
  Proposed Resolution:
    The footnote should be removed.
    The WP should be explicit and say that const and volatile semantics
    are never used on the object being constructed or destroyed in a
    constructor or destructor member function.  The const and volatile
    semantics apply to const/volatile objects only once their
    initialization has completed and only until their destruction
    starts.
 
562 - Is a copy constructor a conversion function?
 
  Is a copy constructor a conversion function?
  Are implicitly-declared copy constructor explicit or not?
  This subclause should be made clearer.
 
  Proposed Resolution:
    Yes, a copy constructor is a conversion function
    (as implied by 4.12[conv.class]).
    An implicitly-declared copy constructor is not an explicit
    conversion constructor; it can be used for implicit type conversion.
 
574 - Must const members and reference members be initialized in the
      ctor-initializer of their owning class?
 
    struct B {
            const int i;
            B();
    };
    B() { } // should this be ill-formed because the const member 'i' is
            // not initialized by the constructor ctor-initializer?
 
  Proposed Resolution:
    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 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 for X 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."
 
478 - can a union constructor initialize multiple members?
 
    union U {
      int i;
      float f;
      U() : i(55), f(99.9) { } // Is this valid?
    };
 
  I see two solutions:
    1) it is ill-formed to initialize two members.
    2) it is well-formed to initialize two members.  Members are
       initialized in the order their declaration appears in the union
       member list and the last member that is initialized gives the
       union its initial value.
 
  Proposed Resolution:
    I don't really care.
 
534 - Can the members of an anonymous union be named in a
      ctor-initializer?
 
    struct X {
            union { int i; float f; };
            X() : f(1.0); // Is this valid?
    };
 
  Proposed Resolution:
    Yes.
 
95 - Volatility, copy constructors, and assignment operators.
 
  Section 12.1p5 of the WP says:
    "A copy constructor for a class X is a constructor whose first
     argument is of type X& or const X&..."
 
    What happens if a user passes volatile objects (by reference) to
    copy constructors and/or assignment operators?
 
  The WP is already quite clear that the implicitly-declared constructor
  and assignment operators not able to copy volatile objects.
  Footnote 82) says:
    "This implies that the reference parameter of the
     implicitly-declared copy constructor cannot bind to a volatile
     lvalue".
  Footnote 84) says something similar for implicitly-declared
  assignment operators.
 
  However, what happens if, in a class hierarchy, some classes have
  user-declared copy constructors and/or assignment operators that
  accept volatile arguments? i.e.
 
    class B1 { };
    class B2 { B(volatile B2 &); };
    class D : public B1, B2 { };
 
    volatile D d1;
    volatile D d2(d1); // which copy constructor is used?
 
  Is the copy constructor for D implicitly declared or must the user
  explicitly declare a copy constructor for D that accepts a volatile
  argument?  Which base class copy constructor is implicitly called when
  the derived class copy constructor is defined?  If the user provides a
  copy constructor for D that accepts volatile arguments, will it
  implicitly call the "ordinary" copy constructor for B1 or must a copy
  constructor for B1 accepting volatile arguments also be provided?
 
  Proposed Resolution:
    If a volatile object is used as the argument for a copy constructor
    or assignment operator, a copy constructor or assignment operator
    accepting volatile arguments must be explicitly defined for the
    class type and for all of its base classes, i.e.
 
    in the example above, copy constructors accepting volatile
    arguments must be provided for the classes D and B1, otherwise the
    program is ill-formed.