This is an unofficial snapshot of the ISO/IEC JTC1 SC22 WG21 Core Issues List revision 113d. See http://www.open-std.org/jtc1/sc22/wg21/ for the official list.

2024-04-05


413. Definition of "empty class"

Section: Clause 11  [class]     Status: CD1     Submitter: Pete Becker     Date: 30 Apr 2003

[Voted into WP at April, 2007 meeting.]

The proposal says that value is true if "T is an empty class (10)". Clause 10 doesn't define an empty class, although it has a note that says a base class may "be of zero size (clause 9)" 9/3 says "Complete objects and member subobjects of class type shall have nonzero size." This has a footnote, which says "Base class subobjects are not so constrained."

The standard uses the term "empty class" in two places (9.4.2 [dcl.init.aggr]), but neither of those places defines it. It's also listed in the index, which refers to the page that opens clause 9, i.e. the nonzero size stuff cited above.

So, what's the definition of "empty class" that determines whether the predicate is_empty is true?

The one place where it's used is 9.4.2 [dcl.init.aggr] paragraph 8, which says (roughly paraphrased) that an aggregate initializer for an empty class must be "{}", and when such an initializer is used for an aggregate that is not an empty class the members are default-initialized. In this context it's pretty clear what's meant. In the type traits proposal it's not as clear, and it was probably intended to have a different meaning. The boost implementation, after it eliminates non-class types, determines whether the trait is true by comparing the size of a class derived from T to the size of an otherwise-identical class that is not derived from T.

Howard Hinnant: is_empty was created to find out whether a type could be derived from and have the empty base class optimization successfully applied. It was created in part to support compressed_pair which attempts to optimize away the space for one of its members in an attempt to reduce spatial overhead. An example use is:

  template <class T, class Compare = std::less<T> >
  class SortedVec
  {
  public:
  ...
  private:
    T* data_;
    compressed_pair<Compare, size_type> comp_;

    Compare&       comp()       {return comp_.first();}
    const Compare& comp() const {return comp_.first();}
    size_type&     sz()         {return comp_.second();}
    size_type      sz() const   {return comp_.second();}
  };

Here the compare function is optimized away via the empty base optimization if Compare turns out to be an "empty" class. If Compare turns out to be a non-empty class, or a function pointer, the space is not optimized away. is_empty is key to making this work.

This work built on Nathan's article: http://www.cantrip.org/emptyopt.html.

Clark Nelson: I've been looking at issue 413, and I've reached the conclusion that there are two different kinds of empty class. A class containing only one or more anonymous bit-field members is empty for purposes of aggregate initialization, but not (necessarily) empty for purposes of empty base-class optimization.

Of course we need to add a definition of emptiness for purposes of aggregate initialization. Beyond that, there are a couple of questions:

  1. Should the definition of emptiness used by the is_empty predicate be defined in a language clause or a library clause?
  2. Do we need to open a new core issue pointing out the fact that the section on aggregate initialization does not currently say that unnamed bit-fields are skipped?

Notes from the October, 2005 meeting:

There are only two places in the Standard where the phrase “empty class” appears, both in 9.4.2 [dcl.init.aggr] paragraph 8. Because it is not clear whether the definition of “empty for initialization purposes” is suitable for use in defining the is_empty predicate, it would be better just to avoid using the phrase in the language clauses. The requirements of 9.4.2 [dcl.init.aggr] paragraph 8 appear to be redundant; paragraph 6 says that an initializer-list must have no more initializers than the number of elements to initialize, so an empty class already requires an empty initializer-list, and using an empty initializer-list with a non-empty class is covered adequately by paragraph 7's description of the handling of an initializer-list with fewer initializers than the number of members to initialize.

Proposed resolution (October, 2005):

  1. Change 9.4.2 [dcl.init.aggr] paragraph 5 by inserting the indicated text:

  2. Static data members and anonymous bit fields are not considered members of the class for purposes of aggregate initialization. [Example:

        struct A {
            int i;
            static int s;
            int j;
            int :17;
            int k;
        } a = { 1 , 2 , 3 };
    

    Here, the second initializer 2 initializes a.j and not the static data member A::s, and the third initializer 3 initializes a.k and not the padding before it.end example]

  3. Delete 9.4.2 [dcl.init.aggr] paragraph 8:

  4. An initializer for an aggregate member that is an empty class shall have the form of an empty initializer-list {}. [Example:

        struct S { };
        struct A {
            S s;
            int i;
        } a = { { } , 3 };
    

    end example] An empty initializer-list can be used to initialize any aggregate. If the aggregate is not an empty class, then each member of the aggregate shall be initialized with a value of the form T() (7.6.1.4 [expr.type.conv]), where T represents the type of the uninitialized member.

This resolution also resolves issue 491.

Additional note (October, 2005):

Deleting 9.4.2 [dcl.init.aggr] paragraph 8 altogether may not be a good idea. It would appear that, in its absence, the initializer elision rules of paragraph 11 would allow the initializer for a in the preceding example to be written { 3 } (because the empty-class member s would consume no initializers from the list).

Proposed resolution (October, 2006):

(Drafting note: this resolution also cleans up incorrect references to syntactic non-terminals in the nearby paragraphs.)

  1. Change 9.4.2 [dcl.init.aggr] paragraph 4 as indicated:

    An array of unknown size initialized with a brace-enclosed initializer-list containing n initializers initializer-clauses, where n shall be greater than zero... An empty initializer list {} shall not be used as the initializer initializer-clause for an array of unknown bound.
  2. Change 9.4.2 [dcl.init.aggr] paragraph 5 by inserting the indicated text:

    Static data members and anonymous bit fields are not considered members of the class for purposes of aggregate initialization. [Example:

        struct A {
            int i;
            static int s;
            int j;
            int :17;
            int k;
        } a = { 1 , 2 , 3 };
    

    Here, the second initializer 2 initializes a.j and not the static data member A::s, and the third initializer 3 initializes a.k and not the anonymous bit field before it.end example]

  3. Change 9.4.2 [dcl.init.aggr] paragraph 6 as indicated:

    An initializer-list is ill-formed if the number of initializers initializer-clauses exceeds the number of members...
  4. Change 9.4.2 [dcl.init.aggr] paragraph 7 as indicated:

    If there are fewer initializers initializer-clauses in the list than there are members...
  5. Replace 9.4.2 [dcl.init.aggr] paragraph 8:

    An initializer for an aggregate member that is an empty class shall have the form of an empty initializer-list {}. [Example:

        struct S { };
        struct A {
            S s;
            int i;
        } a = { { } , 3 };
    

    end example] An empty initializer-list can be used to initialize any aggregate. If the aggregate is not an empty class, then each member of the aggregate shall be initialized with a value of the form T() (7.6.1.4 [expr.type.conv]), where T represents the type of the uninitialized member.

    with:

    If an aggregate class C contains a subaggregate member m that has no members for purposes of aggregate initialization, the initializer-clause for m shall not be omitted from an initializer-list for an object of type C unless the initializer-clauses for all members of C following m are also omitted. [Example:

        struct S { } s;
        struct A {
            S s1;
            int i1;
            S s2;
            int i2;
            S s3;
            int i3;
        } a = {
            { },   // Required initialization
            0,
            s,     // Required initialization
            0
        };         // Initialization not required for A::s3 because A::i3 is also not initialized
    

    end example]

  6. Change 9.4.2 [dcl.init.aggr] paragraph 10 as indicated:

    When initializing a multi-dimensional array, the initializers initializer-clauses initialize the elements...
  7. Change 9.4.2 [dcl.init.aggr] paragraph 11 as indicated:

    Braces can be elided in an initializer-list as follows. If the initializer-list begins with a left brace, then the succeeding comma-separated list of initializers initializer-clauses initializes the members of a subaggregate; it is erroneous for there to be more initializers initializer-clauses than members. If, however, the initializer-list for a subaggregate does not begin with a left brace, then only enough initializers initializer-clauses from the list are taken to initialize the members of the subaggregate; any remaining initializers initializer-clauses are left to initialize the next member of the aggregate of which the current subaggregate is a member. [Example:...
  8. Change 9.4.2 [dcl.init.aggr] paragraph 12 as indicated:

    All implicit type conversions (7.3 [conv]) are considered when initializing the aggregate member with an initializer from an initializer-list assignment-expression. If the initializer assignment-expression can initialize a member, the member is initialized. Otherwise, if the member is itself a non-empty subaggregate, brace elision is assumed and the initializer assignment-expression is considered for the initialization of the first member of the subaggregate. [Note: As specified above, brace elision cannot apply to subaggregates with no members for purposes of aggregate initialization; an initializer-clause for the entire subobject is required. —end note] [Example:... Braces are elided around the initializer initializer-clause for b.a1.i...
  9. Change 9.4.2 [dcl.init.aggr] paragraph 15 as indicated:

    When a union is initialized with a brace-enclosed initializer, the braces shall only contain an initializer initializer-clause for the first member of the union...
  10. Change 9.4.2 [dcl.init.aggr] paragraph 16 as indicated:

    [Note: as As described above, the braces around the initializer initializer-clause for a union member can be omitted if the union is a member of another aggregate. —end note]

(Note: this resolution also resolves issue 491.)