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.