Source: http://stannum.co.il/blog/4/legalization-of-empty-objects
Timestamp: 2019-04-22 19:00:12+00:00

Document:
The canonical explanation of sizeof(E) == 1 comes from BS himself, who says that it’s specified so ‘to ensure that the addresses of two different objects will be different’. However, despite an intensive search, I couldn’t find anywhere an explanation of why this requirement is necessary for empty objects.
If we think of E as 10 distinct objects of type E, then the first loop is the correct way to iterate. This is the traditional way of iterating over a contiguously allocated sequence. Pointers wouldn‘t do as iterators of C arrays anymore as we would need a base pointer plus an index to reference each individual element in the array.
Alternatively, we can define that for empty types E[n] is the same as E for all n (sure, empty arrays aren‘t permitted in C++, but once we allow zero-sized types we can fix that as described bellow). In such case the second loop would be the correct way to iterate. This is also the idiomatic STL approach to iterate over sequences, and we could continue using native pointers as iterators over C arrays.
This is a tough dilemma which I still cannot decide. Each time I think about it my brain explodes in a division by zero accident.
Yet not all is lost. Notice that all the problems listed above stem in arrays of empty types, but the main use of empty types is not in their arrays. Instead they are heavily used, even unknowingly, as template arguments (e.g. the default std::vector allocator) or extra function parameters for overload resolution (e.g. iterator category tags).
As a wildly used advanced language that recognizes zero-based indices it is strange that C++ still struggles recognizing the concept of emptiness. Let‘s fix this with some language surgery. See the exact standardese wording in the end of the article.
We allow empty objects to have zero size. We allow creating such objects but not performing pointer arithmetic on them or creating arrays thereof. Empty base optimization is not longer needed, because empty objects are now permitted to be densely packed into the same address, even if they are of the same type. The constructors and destructors of such objects are called per each declared instance, even if it means calling the same constructor or destructor with the same this address twice.
Principle of least surprise. As experience shows, people are surprised by sizeof(E) == 1, EBO, and that empty arrays are disallowed by the standard even though they are supported by multiple compilers. I expect that the new rules better match people‘s expectations.
What you don’t use, you don‘t pay for. I don‘t want to pay with extra bytes plus alignment for empty objects when they arise—usually as parameters to generic code. If somebody really needs to guarantee uniqueness of empty object addresses, e.g. to insert/delete them into a global registry map, she can always state this explicitly by making the object non-empty through insertion of a dummy char member.
Uniformity. There shall be as few special cases as possible: in user-code, in the library implementation, in the compiler and in the standard, in that order of preference. Currently to avoid paying for empty objects in generic code, the author of that code is responsible for employing EBO. It appears that when we increase uniformity of the standard by extending the ‘natural’ behavior to the edge cases, then the compiler, the library and the user-code get simpler.
If it doesn‘t make sense, leave it undefined or ill-formed. This is where the restrictions on the empty types stem.
Currently ISO C++ violates the first three principles, and this is what I am trying to fix here.
This is a diff relative to the WG21 N3337 core. Due to the scope of the change there might be a lot of places I missed or other inconsistencies. This diff permits empty objects to be of zero size, but does not require them to be so. Note that EBO isn‘t mandatory in ISO C++ either.
Replace all incompletely-defined object type with incomplete type.
Replace all object type or void with object type.
Replace all incomplete object type or void with incomplete type.
§1.8/5 Unless it is a bit-field (9.6), a most derived object shall have a non-zero size and shall occupy one or more bytes of storage. Base class subobjects may have zero size. Objects occupy zero or more bytes of storage.
§3.9/5 A class that has been declared but not defined, or an array of unknown size or of incomplete element type, is an incompletely-defined object type.43 Incompletely-defined object types and the void types are incomplete types (3.9.1).
§3.9/8 An object type is a (possibly cv-qualified) type that is not a function type, and not a reference type, and not a void type.
§3.9/9 Arithmetic types (3.9.1), enumeration types, pointer types, pointer to member types (3.9.2), std::nullptr_t, void, and cv-qualified versions of these types (3.9.3) are collectively called scalar types.
§3.9/9+ An empty type is an empty class (Clause 9) or void.
§3.9.1/9 The void type has an empty set of values. The void type is an incomplete type that cannot be completed. It is used as the return type for functions that do not return a value. Any expression can be explicitly converted to type cv void (5.4). An expression of type void shall be used only as an expression statement (6.2), as an operand of a comma expression (5.18), as a second or third operand of ?: (5.16), as the operand of typeid or decltype, as the expression in a return statement (6.6.3) for a function with the return type void, or as the operand of an explicit conversion to type cv void.
§5.2.1/1 The type “T” shall be a completely-defined non-empty object type.
§5.2.6/1 The type of the operand shall be an arithmetic type or a pointer to a complete non-empty object type.
§5.3.2/1 The type of the operand shall be an arithmetic type or a pointer to a completely-defined non-empty object type.
§5.3.3/2 The size of a most derived class shall be greater than zero (1.8).
§5.7/1 For addition, either both operands shall have arithmetic or unscoped enumeration type, or one operand shall be a pointer to a completely-defined non-empty object type and the other shall have integral or unscoped enumeration type.
— the left operand is a pointer to a completely-defined non-empty object type and the right operand has integral or unscoped enumeration type.
§5.17/7 In += and -=, E1 shall either have arithmetic type or be a pointer to a possibly cv-qualified completely-defined non-empty object type.
§8.3.4/1 T is called the array element type; this type shall not be a reference type, the (possibly cv-qualified) type void, a function type, or an abstract class type or a (possibly cv-qualified) empty type. If the constant-expression (5.19) is present, it shall be an integral constant expression and its value shall be greater than or equal to zero.
— has no non-empty base classes.
§10/8 A base class subobject may be of zero size (Clause 9); however, two subobjects that have the same class type and that belong to the same most derived object must not be allocated at the same address (5.10).
C++ still suffers from a lack of uniformity when it comes to edge cases and built-in types. I have shown how it is possible to increase uniformity and simplify the language. Unfortunately everything comes at a price: the proposed modifications are incompatible with the current language standard, and as such they are unlikely to be ever adopted. One should, however, consider this in other language designs.
Even the innocent definition of a above requires calling constructors and destructors of E equally many times, even if one of them throws in the middle. Think how the compiler would do that.

References: §1

§3

§3

§3

§3

§3

§5

§5

§5

§5

§5

§5

§8

§10