This is an unofficial snapshot of the ISO/IEC JTC1 SC22 WG21 Core Issues List revision 115b. See http://www.open-std.org/jtc1/sc22/wg21/ for the official list.
2024-08-20
[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:
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):
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 padding before it. —end example]
Delete 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.
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.)
Change 9.4.2 [dcl.init.aggr] paragraph 4 as indicated:
An array of unknown size initialized with a brace-enclosed initializer-list containing ninitializersinitializer-clauses, where n shall be greater than zero... An empty initializer list {} shall not be used as theinitializerinitializer-clause for an array of unknown bound.
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]
Change 9.4.2 [dcl.init.aggr] paragraph 6 as indicated:
An initializer-list is ill-formed if the number ofinitializersinitializer-clauses exceeds the number of members...
Change 9.4.2 [dcl.init.aggr] paragraph 7 as indicated:
If there are fewerinitializersinitializer-clauses in the list than there are members...
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]
Change 9.4.2 [dcl.init.aggr] paragraph 10 as indicated:
When initializing a multi-dimensional array, theinitializersinitializer-clauses initialize the elements...
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 ofinitializersinitializer-clauses initializes the members of a subaggregate; it is erroneous for there to be moreinitializersinitializer-clauses than members. If, however, the initializer-list for a subaggregate does not begin with a left brace, then only enoughinitializersinitializer-clauses from the list are taken to initialize the members of the subaggregate; any remaininginitializersinitializer-clauses are left to initialize the next member of the aggregate of which the current subaggregate is a member. [Example:...
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 aninitializer from an initializer-listassignment-expression. If theinitializerassignment-expression can initialize a member, the member is initialized. Otherwise, if the member is itself anon-emptysubaggregate, brace elision is assumed and theinitializerassignment-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 theinitializerinitializer-clause for b.a1.i...
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 aninitializerinitializer-clause for the first member of the union...
Change 9.4.2 [dcl.init.aggr] paragraph 16 as indicated:
[Note:asAs described above, the braces around theinitializerinitializer-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.)