Topic: const constructor vs. constructor const: Proposal


Author: climate@on.bell.ca (Mike Whiten)
Date: 1997/05/29
Raw View
Steve White (white@ctf.com) wrote:

: The constructor const
: ---------------------

: Rather, I will add my voice to those calling for another constructor
: that does not exist in C++, which I will call a "constructor const"
: to distinguish it from the const constructor.  Such a constructor
: would be free to change its member variables, but would create an
: object of const type.

This is precisely what I meant by a "const constructor".  You and I
are asking for the same thing from the language.  Below I clarify why
I prefer what you call the "const constructor" style.


: A constructor const might be declared like this:

:     class B
:     {};
:     class A
:     {
:         public:
:             const A( const B & b );    // constructor const
:     };
:
: Its meaning would be that an object created by it is of const type,
: so that if we write
:
:     const B    b;
:     const A    a1( b );        // is legal, whereas
:     A          a2( b );        // is a syntax error,

This strays from the way we use "const methods".  In many cases,
we don't care if the object a const method is invoked upon is
const or non-const.  That's why we have the rule of thumb of
making all member functions const unless they change the state
of the (logical) object.

I would like "constructor const's" to conform to this.  That is
to say, it can be used to construct both const and non-const
objects.  It's just that (for the lifetime of the constructor),
the object [*this] will temporarily be treated as const.

If we need two types of constructors, we can supply a const and
a non-const constructor.  If we want the syntax error as in
your example, we make the non-const constructor private.

: The body of a constructor const should treat the object as
: non-const, since its primary purpose is to initialize the object's
: members.

This is where you and I disagree.  What you are proposing is applying
the idea of a "const member function" to constructors.  I agree that this
is useful.  What a const member function is, however, is a method where
the implicit pointer "this" is of type "const T*".  If we extend this
to constructors, in a "constructor const" (by any other name) we will
not be able to assign to contained members of this object because they
will also be const.

This is not necessarily a bad thing, because we can still *initialize*
the members (you may not be able to assign to const objects, but you
certainly can initialize them).  Although this limits to a small
degree the way we write our constructor const, it's not really an
obstacle to anyone who can write a half decent C++ expression.

What I'm proposing is a constructor (using my syntax) where

    SomeClass( ... ) const
       : mem1( ... ),  mem2( ... )  // can be written to here
    {
        /* cannot be written to here ... the object is already
    constructed */
    }

Even though (in this constructor) mem1 and mem2 are "const", they
can still be initialized (with any expression) in the initializer
list.  However, by the time we hit the opening curly brace, the
object is fully built... any attempt to write to the members would
constitute assignment and would be an error.

: Subclassing, overloading, and default constructors
: --------------------------------------------------

: It seems best to handle constructor const overloading in the same
: way that member function overloading is currently handled. It
: should be a syntax error for a class to have a constructor
: and a constructor const with the same arguments.

Precisely... the "argument" by which these two constructors are
being overloaded is the implicit "this" pointer.

: The constructor const introduces a few problems having to do with
: subclassing and default constructors.  There is more than one way
: to deal with these problems, but I will suggest here a few
: restrictions that will make the constructor const consistent with
: the extant language, at the expense of some flexibility in the use
: of constructor const.

: First, if a constructor const were to invoke a constructor const of
: a base class, there would be a problem with whether or not to
: consider the base class part of the object to be const within the
: body of the constructor.  The question is avoided with this
: restriction:

With the proposal I favour, this problem will be minimized.  If a
"const constructor" (which I deem as being the same thing as your
"constructor const") is written such that it can build both const
objects and non-const, then you only need one.  We don't require a
restrictive rule here as this is simply an issue of design and
craft.

: A constructor of a derived class may not invoke a constructor const
: of a base class.

: This restriction implies that if a class has a constructor const
: that takes no arguments, then the default constructor for that
: class cannot be generated by the compiler.  The effect this has for
: the user of the class is merely that if an object is created with
: no arguments, it is const.

I think these restrictions are unnecessary in the proposal I had
in mind.  The functionality I seek is a general scheme.  The
functionality you seek is a specific form of usage of the proposal
I made.

: The resolution of the corresponding problem for copy constructors
: is a bit more complicated, because it is permissible for a class X
: to have copy constructors that take arguments of type X& and const
: X&.
:
: To avoid a default copy constructor calling a constructor const of
: the base class, we make these restrictions:
:
: If a class X has a copy constructor const declared as
:
:     const X( const X & ),
:
: then any default copy constructor of X or its subclasses must take a
: non-const argument.
:
: If a class X has a copy constructor const declared as
:
:     const X( X & ),
:
: then any default copy constructor of X or its subclasses must take a
: const constructor.  According to the ARM (12.8), this can only
: happen if all base classes of X have constructors that take a single
: argument of const X &.

In the scheme I favour, any base class non-const constructors would
generate an error at this point.  In the canon of C++ style, all member
functions should be "const" unless you have a good reason for it
not to be const.  Similarly, all inheritence should be "virtual"
unless you have a good reason for it not to be.  I believe that
all constructors should be "const" unless you *need* to perform
assigment (in the constructor body... as opposed to initialization
in the initializer list).

: If among a class X and any of its base classes both types of copy
: constructor const are declared, then the compiler can not generate
: any default copy constructor for X or any of its subclasses.

In my scheme, the default (I call it compiler-generated) copy
constructor is a const constructor (ie. it performs no assignment,
only initialization).

: Justification for term "constructor const"
: ------------------------------------------
:
: Member functions that can only be called from a const instantiation
: are referred to as "const methods"; such constructors cannot change
: member variables of the object.  So the term "const constructor"
: would best fit a constructor that could not change the member
: variables of the object.
:
: The term constructor const could be read as "constructor of const".
: The notation proposed here parallels the notation for member
: functions that return const objects. Note that like other
: constructors, the constructor const does not return anything; it
: initializes an object.

   My view is that the object being constructed already has a
   type (either T or const T).  Within the body of the constructor,
   we treat "*this" as if it were of type "T" even if the object
   were in fact "const T".

   To me, this is inconsistent.  If we apply the "const method"
   thinking to constructors, then within the body of the constructor,
   the type of "*this" is consistent with the type of the object
   *actually* being built.  At the very least, we are only adding
   temporary const'ness to it (a standard C++ idiom when passing
   parameters by pointers or ref).

   If we really truly need a constructor that performs assignment,
   then we can write a non-const constructor in the traditional
   C++ style.

: I will not insist on the adoption of the prefix keyword "const" in
: the declaration of the constructor const, but note that it would
: not break any existing code, since the const keyword is not
: currently used with constructors.

: An objection concerning construction of const objects
: -----------------------------------------------------

: How can the constructor const change the members of the object when
: the object it is constructing is const?

: The ARM (12.1) makes this point clearly:  There is no object until
: the constructor exits--the constructor acts on raw memory to
: produce an object.  This memory has no type, and so in principle,
: no const-ness.  Just as it is acceptable for any constructor to
: initialize const member variables and member references, it should
: be acceptable for a constructor const to initialize the members of
: the object.  We must only agree on the syntax by which we tell it
: to do this.

  Both the existing behaviour of the language and your proposal
  require a bit of fudging of the type system within a constructor.
  The scheme I had in mind eliminates this fudging and conforms more
  closely with the traditional behaviour of the C++ type system.

  Currently, constructors ignore the const'ness (and most likely,
  the volatile'ness (<g>)) of the type of the object being
  created.  This allows us to both initialize *and* assign to the
  members of a "const" object.  To me this is an unnecessary
  inconsitency.

  In your scheme, the const/volatile'ness of the object being
  creates plays a role, but again we ignore it. [If we generalize
  this to 'volatile' as well as 'const' would we *want* to
  ignore the volatility of a data member?]

  In my scheme, the type of 'this' within the constructor call
  is "const T*" and the type system is enforced.  This allows us
  to:
  a) initialize data members but not assign to them
  b) overload based on the implicit argument "this"

  If a "const constructor" is sufficient for non-const objects,
  it can be used to create them as well.


: An objection from the ARM
: -------------------------
:
: The ARM (12.1) says that a constructor cannot be declared const.
:
:     "A constructor may not be declared const or volatile.
:     A constructor may not be virtual.  A constructor may not be
:     static."
:
: Convincing arguments are given against a virtual constructor
: declaration.  Elsewhere, there are arguments about low-level
: implementation of constructors that satisfy me that a constructor
: should not be declared volatile or static.

A volatile constructor makes sense to me.  It won't optimize out any reads
from members.

: I have not found any argument against a constructor being declared
: const, however.

: The reasons against the "storage class" volatile and static
: constructor declarations have to do with implementation,
: specifically with how, when, and where the object is stored.

Although "static" and "volatile" are called "storage class specifiers"
I think this term is anachronist (My compiler taught me that word!).
To me a storage class is a classification of the storage of an object.
Primarily it tells me the lifespan of the object.  "static" doesn't
always affect the storage of something... it's been overloaded within
the language to mean *other* things (used in front of a function
declaration, it simply means the name of the function is not
published to the linker... has nothing to do with classifications
of storage).

: The "constructor const" described here is purely syntactic, as
: opposed to a storage class declaration, however.  It specifies the
: circumstances under which it will be called, and type of the
: resulting object, rather than storage details.

  Application of "const" to the end of a member function declaration
  is more than syntax.  It defines the type of "this" within
  that function.

  *whew*

I'm gonna have a little lie-down now...

Mike
---
[ comp.std.c++ is moderated.  To submit articles: try just posting with      ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu         ]
[ FAQ:      http://reality.sgi.com/employees/austern_mti/std-c++/faq.html    ]
[ Policy:   http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu                             ]





Author: white@ctf.com (Steve White)
Date: 1997/05/28
Raw View
This group has seen a running debate concerning "const
constructors". The meaning of the the term changes from one post to
another.

I will discuss a useful idea that is sometimes confused with the
const constructor but that would be easy to implement, and whose
meaning is clear.

The const constructor
---------------------

The controversial "const constructor" is usually presented as
something like a constructor that in some sense can't change its
member variables, or produces a const object, with a declaration
something like

    A() const;

It is not my intention to argue here whether some useful sense can
be made of the const constructor.

The constructor const
---------------------

Rather, I will add my voice to those calling for another constructor
that does not exist in C++, which I will call a "constructor const"
to distinguish it from the const constructor.  Such a constructor
would be free to change its member variables, but would create an
object of const type.

The constructor const is meant to remedy the problem of propagation
of const type through constructor calls.  With the constructor
const, the programmer has better control of the type of temporary
objects.

Syntax and usage
----------------

A constructor const might be declared like this:

    class B
    {};
    class A
    {
        public:
            const A( const B & b );    // constructor const
    };

Its meaning would be that an object created by it is of const type,
so that if we write

    const B    b;
    const A    a1( b );        // is legal, whereas
    A          a2( b );        // is a syntax error,

and in case the function f is overloaded as

    void f( const A & );
    void f( A & );

then in the expression

    f( b )

the temporary object created by the conversion to A is guaranteed
to be of const type, and so the first overload of f is the one
chosen.  It is this behaviour that makes the constructor const
useful.

The body of a constructor const should treat the object as
non-const, since its primary purpose is to initialize the object's
members.

In the rare case when operator new is to be called for a constructor
const, the syntax should be that new creates a pointer of const
type, so that

    const A    *a = new A( b );    // is legal, whereas
    A          *a = new A( b );    // is a syntax error.

Subclassing, overloading, and default constructors
--------------------------------------------------

It seems best to handle constructor const overloading in the same
way that member function overloading is currently handled. It
should be a syntax error for a class to have a constructor
and a constructor const with the same arguments.

The constructor const introduces a few problems having to do with
subclassing and default constructors.  There is more than one way
to deal with these problems, but I will suggest here a few
restrictions that will make the constructor const consistent with
the extant language, at the expense of some flexibility in the use
of constructor const.

First, if a constructor const were to invoke a constructor const of
a base class, there would be a problem with whether or not to
consider the base class part of the object to be const within the
body of the constructor.  The question is avoided with this
restriction:

A constructor of a derived class may not invoke a constructor const
of a base class.

This restriction implies that if a class has a constructor const
that takes no arguments, then the default constructor for that
class cannot be generated by the compiler.  The effect this has for
the user of the class is merely that if an object is created with
no arguments, it is const.

The resolution of the corresponding problem for copy constructors
is a bit more complicated, because it is permissible for a class X
to have copy constructors that take arguments of type X& and const
X&.

To avoid a default copy constructor calling a constructor const of
the base class, we make these restrictions:

If a class X has a copy constructor const declared as

    const X( const X & ),

then any default copy constructor of X or its subclasses must take a
non-const argument.

If a class X has a copy constructor const declared as

    const X( X & ),

then any default copy constructor of X or its subclasses must take a
const constructor.  According to the ARM (12.8), this can only
happen if all base classes of X have constructors that take a single
argument of const X &.

If among a class X and any of its base classes both types of copy
constructor const are declared, then the compiler can not generate
any default copy constructor for X or any of its subclasses.

For a precedent for situations in which the compiler cannot generate
a default constructor, see "The C++ Programming Language", 2nd Ed.,
r.12.8.

Justification for term "constructor const"
------------------------------------------

Member functions that can only be called from a const instantiation
are referred to as "const methods"; such constructors cannot change
member variables of the object.  So the term "const constructor"
would best fit a constructor that could not change the member
variables of the object.

The term constructor const could be read as "constructor of const".
The notation proposed here parallels the notation for member
functions that return const objects. Note that like other
constructors, the constructor const does not return anything; it
initializes an object.

I will not insist on the adoption of the prefix keyword "const" in
the declaration of the constructor const, but note that it would
not break any existing code, since the const keyword is not
currently used with constructors.

An objection concerning construction of const objects
-----------------------------------------------------

How can the constructor const change the members of the object when
the object it is constructing is const?

The ARM (12.1) makes this point clearly:  There is no object until
the constructor exits--the constructor acts on raw memory to
produce an object.  This memory has no type, and so in principle,
no const-ness.  Just as it is acceptable for any constructor to
initialize const member variables and member references, it should
be acceptable for a constructor const to initialize the members of
the object.  We must only agree on the syntax by which we tell it
to do this.

An objection from the ARM
-------------------------

The ARM (12.1) says that a constructor cannot be declared const.

    "A constructor may not be declared const or volatile.
    A constructor may not be virtual.  A constructor may not be
    static."

Convincing arguments are given against a virtual constructor
declaration.  Elsewhere, there are arguments about low-level
implementation of constructors that satisfy me that a constructor
should not be declared volatile or static.

I have not found any argument against a constructor being declared
const, however.

The reasons against the "storage class" volatile and static
constructor declarations have to do with implementation,
specifically with how, when, and where the object is stored.

The "constructor const" described here is purely syntactic, as
opposed to a storage class declaration, however.  It specifies the
circumstances under which it will be called, and type of the
resulting object, rather than storage details.

An example from the GNU class library
-------------------------------------

The need for the constructor const usually arises when an object is
created to refer to another object, as in the case of a substring
of a string or a subarray of an array.  In these circumstances,
there is a problem with the propagation of const-ness in
constructors.

As a concrete example, take the String class from the GNU C++
Library.  In the SubString section of String.h, we find the
following comment:

    // Note that you can't take a substring of a const String, since
    // this leaves open the possiblility of indirectly modifying the
    // String through the SubString

The SubString class has two protected constructors

    SubString(String& x, int p, int l);
    SubString(const SubString& x);

These constructors allow for manipulating a substring of a String as
a String without copying the characters of the String.

One would like to have a similar construct for reading a substring
of a const String without copying the characters.  This can't be
done in C++ without compromising the const-ness of the original
String because there is no way to insist that the resulting
SubString is const.

If one could specify a constructor const SubString, perhaps as

    const SubString( const String & x, int p, int l );

this problem would disappear.  It would be clear that if the
original string is const, it is a syntax error to modify the
SubString.

Alternatives
------------

There exist alternatives to the constructor const.  Let's look at a
couple of them.

Without a constructor const for the SubString, we must write a
function to do the job.  Something like

    // member of String version:
        const SubString ConstSubString( int first,
                                        int last ) const;

    // friend of SubString version:
        const SubString ConstSubString( const String &,
                                        int first, int last );

Objections to these work-arounds run along these lines:

(1) Efficiency.  In either case, a copy of a SubString object must
be made, rather than a single constructor const being called.

(2) Readability.  The need to invoke both SubString objects and some
other function that creates const substring objects makes for
confusing reading.  The user of the classes must know about and make
a deliberate distinction between creating a substring and a const
substring.

(3) Modularity and namespace cluttering:  The substring class and
the function must have different names.  In the "method of String"
approach, the String class must have knowledge of SubString.  ( The
"friend of SubString" alternative is superior in this way to the
"member of String", because it does not require a priori that the
String class have knowledge of SubString. )

Conclusion
----------

I have run into the vexatious problem of propagation of const-ness a
couple of times now.  Each time, I had a clear idea of the
interface I wanted to present to the user, and was forced to resort
to disappointingly complex means to cover this one inadequacy of
the language.

Please feel free to let me know of any holes you find in this proposal.
Of course, I hope this idea is sound and will prove sufficiently useful
to appear in some future standard of the language, but it is at least
an interesting exercise.

The discussions I have referred to can be found on
    <http://www.dejanews.com>
using a power filter that specify comp.std.c++, and searching
for the key words "const" and "ctor".

The issue of constructors invoking constructors const of base
classes raises some interesting questions about the meaning of
const-ness. There is an interesting extension to the notion of a
const object that would permit a constructor to invoke a
constructor const of a base class, but I must confess that I don't
have an immediate use for it.  If anyone is interested, I am
willing to entertain the idea.

Steve White
Data Analyst
CTF Systems, Inc.
http://www.ctf.com
15-1750 McLean Avenue, Port Coquitlam, British Columbia, Canada V3C 1M9
---
[ comp.std.c++ is moderated.  To submit articles: try just posting with      ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu         ]
[ FAQ:      http://reality.sgi.com/employees/austern_mti/std-c++/faq.html    ]
[ Policy:   http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu                             ]