<html>
<head><title>N2354 - Class member initializers</title></head>
<body>

Document Number: N2354=07-0214<br/>
Date: 2007-07-19<br/>
Reply to: Bill Seymour &lt;<a href="mailto:bill-at-the-office@pobox.com">bill-at-the-office@pobox.com</a>&gt;<br/>

<center>

<h1>Class member initializers</h1>
<h3>Michael Spertus<br/>Bill Seymour</h3>
</center>

<h2>Abstract</h2>

We propose allowing the use of initializers for non-static class and
struct data members. The purpose of this is to increase maintainability,
reduce the risk of subtle errors in complex program code, and to make the
use of initializers more consistent.

<p><hr size=5>

<h2>The proposal</h2>

The basic idea is to allow non-static data members of class and struct types to be
initialized where declared. All of the same initialization syntaxes may be used
as for initialization of local variables; and other analyses and suggestions for
improving initialization of local variables as in
<a name="initdocs">
<a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2003/N1493.pdf">N1493</a>,
<a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2003/N1509.pdf">N1509</a>,
<a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2004/N1584.pdf">N1584</a>,
<a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2004/N1701.pdf">N1701</a>,
<a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2005/N1806.html">N1806</a>,
<a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2005/N1824.htm">N1824</a>, and
<a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2005/N1919.pdf">N1919</a>
</a>
may also be applied <i>mutatis mutandis</i> to non-static <tt>class</tt> and
<tt>struct</tt> data members.

<p>As a simple example,
<pre>
    class A {
    public:
        int a = 7;
    };
</pre>
would be equivalent to
<pre>
    class A {
    public:
        A() : a(7) {}
    };
</pre>
The real benefits of member initializers do not become apparent until a class
has multiple constructors. For many data members, especially private ones, all
constructors initialize a data member to a common value as in the next example:
<pre>
    class A {
    public:
        A(): a(7), b(5), hash_algorithm("MD5"), s("class A example") {}
        A(int a_val) : a(a_val), b(5), hash_algorithm("MD5"), s("Constructor run") {}
        A(int b_val) : a(7), b(b_val), hash_algorithm("MD5"), s("Constructor run") {}
        A(D d) : a(f(d)), b(g(d)), hash_algorithm("MD5"), s("Constructor run") {}
        int a, b;
    private:
        // Cryptographic hash to be applied to all A instances
        HashingFunction hash_algorithm;
        // String indicating state in object lifecycle
        std::string s;
    };
</pre>
Even in this simple example, the redundant code is already problematic if the
constructor arguments for <tt>hash_algorithm</tt> are copied incorrectly in one of
<tt>A</tt>&rsquo;s constructors or if one of the lifecycle states was accidentally misspelled as
<tt>"Constructor Run"</tt>. These kinds of errors can easily result in subtle bugs.
Such inconsistencies are readily avoided using member initializers.
<pre>
    class A {
    public:
        A(): a(7), b(5) {}
        A(int a_val) : a(a_val), b(5) {}
        A(int b_val) : a(7), b(b_val) {}
        A(D d) : a(f(d)), b(g(d)) {}
        int a, b;
    private:
        // Cryptographic hash to be applied to all A instances
        HashingFunction hash_algorithm("MD5");
        // String indicating state in object lifecycle
        std::string s("Constructor run");
    };
</pre>

Not only does this eliminate redundant code that must be manually synched, it
makes much clearer the distinctions between the different constructors.
(Indeed, in Java, where both forms of initialization are available,
the use of member initializers is invariably preferred by experienced
Java programmers in examples such as these.)

<p>Now suppose that it is decided that MD5 hashes are not collision resistent
enough and that SHA-1 hashes should be used. Without member initializers, all
the constructors need to be updated. Unfortunately, if one developer is unaware
of this change and creates a constructor that is defined in a different source
file and continues to initialize the cryptographic algorithm to MD5, a very hard
to detect bug will have been introduced. It seems better to keep the information
in one place.

<p>It may happen that a data member will usually have a particular value, but a
few specialized constructors will need to be cognizant of that value. If a
constructor initializes a particular member explicitly, the constructor initialization
overrides the member initializations as shown below:
<pre>
    class A {
    public:
        A(): a(7), b(5) {}
        A(int a_val) : a(a_val), b(5) {}
        A(int b_val) : a(7), b(b_val) {}
        A(D d) : a(f(d)), b(g(d)) {}
        // Copy constructor
        A(const A& aa) : a(aa.a),
                         b(aa.b),
                         hash_algorithm(aa.hash_algorithm.getName()),
                         s(aa.s) {}
        int a, b;
    private:
        // Cryptographic hash to be applied to all A instances
        HashingFunction hash_algorithm("MD5");
        // String indicating state in object lifecycle
        std::string s("Constructor run");
    };
</pre>
A few additional points are worth noting.
<ul>
<li>By allowing non-static data members of classes to be initialized in the same
    way as non-static local variables (which should be thought of as nonstatic
    data members of the function frame), C++ initialization becomes more
    consistent. As many of the <a href="#initdocs">documents mentioned above</a>
    point out, this is much needed.
<p><li>Because these initializers must be given in the class definition, which
    may be included in many files, it is possible that the initializers may vary
    in value depending on context. Although this is troublesome, the same
    problem already exists for default arguments, inline methods, member
    types, etc. Therefore member initializers do not introduce this problem
    and indeed do not appreciably aggravate it.
<p><li>There is some overlap between this proposal and constructor delegation and forwarding
    (<a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2003/N1445.htm">N1445</a>,
    <a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2004/N1581.pdf">N1581</a>,
    <a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2004/N1618.pdf">N1618</a>, and
    <a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2005/N1898.pdf">N1898</a>).
    However, it is easy to see that neither technique obviates the other. For example,
    overriding member initializers appears difficult to do in generality through
    constructor forwarding.  (And note that Java has both member initialization and
    constructor forwarding.  This is not regarded as confusing, and most experienced
    Java programmers make regular use of both techniques based on applicability.)
<p><li>Member initializers make possible the use of copy-initialization for class
    data members. It seems likely this flexibility will prove useful just as in the
    case of local variables.
<p><li>It is intended that this proposal also supports initializer lists
    (<a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2215.pdf">N2215</a>)
    if they are adopted into C++0X.
</ul>

<p><hr size=5>

<h2>Suggested changes to the Working Paper, N2315</h2>

<h4>7.1.5.4 <tt>auto</tt> specifier</h4>

In paragraph [4], change &ldquo;a <i>constant-initializer</i>&rdquo;
to &ldquo;an <i>initializer</i>&rdquo;.

<p><hr size=3>

<h4>9.2 Class Members</h4>

Change the second line of <i>member-declarator</i> which reads
<blockquote>
<i>declarator&nbsp;&nbsp;constant-initializer<sub>opt</sub></i>
</blockquote>
to
<blockquote>
<i>declarator&nbsp;&nbsp;initializer<sub>opt</sub></i>
</blockquote>

<p><hr>

Delete the rule
<blockquote>
<i>constant-initializer:<br><tt>&nbsp;&nbsp;&nbsp;&nbsp;</tt>= constant-expression</i>
</blockquote>

<p><hr>

Delete paragraph 9.2[4] which reads
<blockquote>
A <i>member-declarator</i> can contain a <i>constant-initializer</i> only if it declares
a <tt>static</tt> member (9.4) of <tt>const</tt> integral or <tt>const</tt> enumeration type, see 9.4.2.
</blockquote>

<p><hr size=3>

<h4>9.4.2 Static data members</h4>

In the first sentence of [3] which currently reads
<blockquote>
If a <tt>static</tt> data member is of <tt>const</tt> integral or <tt>const</tt> enumeration type,
its declaration in the class definition may specify a <i>constant-initializer</i>
whose <i>constant-expression</i> shall be an integral constant expression (5.19).
</blockquote>
change
<blockquote>
a <i>constant-initializer</i> whose <i>constant-expression</i> shall be
</blockquote>
to
<blockquote>
an <i>initializer</i> whose <i>initializer-clause</i> or <i>expression-list</i></b> shall be
</blockquote>

<p><hr size=3>

<h4>12.6.2 Initializing bases and members</h4>
Append to the third bullet of 12.6.2[5] which begins &ldquo;Then, non-static data members&rdquo;
<blockquote>
If a constructor has a <i>mem-initializer</i> for a non-static data member
that has an <i>initializer</i>,
the initialization specified by the <i>mem-initializer</i> shall be performed,
and the non-static data member&rsquo;s <i>initializer</i> shall be ignored.
<p>[ <i>Example:</i> given
<pre>
struct A {
    int i = /* some integer expression with side effects */ ;
    A(int arg) : i(arg) { }
    // ...
};
</pre>
the <tt>A(int)</tt> constructor will simply initialize <tt>i</tt> to the value
of <tt>arg</tt>, and the side effects in <tt>i</tt>&rsquo;s initializer
will not take place. &mdash; <i>end example</i> ]
</blockquote>

<p><hr size=3>

<h4>12.8 Copying class objects</h4>
Add before the last sentence of [8] which begins &ldquo;Virtual base class subobjects&rdquo;
<blockquote>
An implicitly declared copy constructor shall ignore any non-static
data member&rsquo;s <i>initializer</i>. [ <i>Note:</i> this implies that
any side effect in such an <i>initializer</i> will not take place.
See also the example in 12.6.2[5]. &mdash; <i>end note</i> ]
</blockquote>

<hr size=5>
</body>
</html>
