3

This is basically a continuation of my prior question.

This is [class]/7 in C++14:

A standard-layout class is a class that:

  • (7.1) — has no non-static data members of type non-standard-layout class (or array of such types) or reference,
  • (7.2) — has no virtual functions (10.3) and no virtual base classes (10.1),
  • (7.3) — has the same access control (Clause 11) for all non-static data members,
  • (7.4) — has no non-standard-layout base classes,
  • (7.5) — either has no non-static data members in the most derived class and at most one base class with non-static data members, or has no base classes with non-static data members, and
  • (7.6) — has no base classes of the same type as the first non-static data member.

Consider the following snippet:

struct B{ int i; };
struct A : B{ int j; };

A satisfies bullet points (7.1) thru (7.4), but doesn't satisfy (7.5), as A has a non-static data member and has a base class with a non-static data member.

What is the problem with A being a standard-layout class?

Edit

As far as I can understand the accepted answer to the question of which this is being considered a dupe, the snippet above would have undefined behavior, if I tried to cast a pointer to A to the first data member of the base class B and back, because of this sentence written by the OP:

Within a class, members are allocated in increasing addresses according to the declaration order. However C++ doesn't dictate the order of allocation for data members across classes.

But that doesn't seem to answer my question. Suppose for example that in a certain compiler implementation, base B would follow struct A in memory, instead of preceding it. But this would contradict the fact that there is an implicit conversion, from a pointer to a derived class, to a pointer to a base class, according to [conv.ptr]/3:

A prvalue of type “pointer to cv D”, where D is a class type, can be converted to a prvalue of type “pointer to cv B”, where B is a base class (Clause 10) of D.

That is, if the base B followed struct A in memory, the above implicit conversion would be invalid.

Community
  • 1
  • 1
Alexander
  • 2,581
  • 11
  • 17
  • 2
    Why would the conversion be invalid? The conversion would just make the appropriate offset and give you the right pointer. – Barry Oct 07 '16 at 19:59
  • @Barry [conv.ptr]/3 does not say anything about an offset. Otherwise, if what you were correct, this would be possible: `A a; A* p = &a; B* q = p;` with `q != p`, i.e., a copy initialization, where the target `q` has a different value than the source `p`. – Alexander Oct 07 '16 at 20:12
  • 1
    That's definitely possible. Consider a class with two non-empty bases where you cast a pointer to the derived class to a pointer to the 2nd base. – Barry Oct 07 '16 at 20:25
  • @Barry You convinced me and also answered my question. Thanks for your insight. – Alexander Oct 08 '16 at 00:19

2 Answers2

2

There are two important guarantees that the standard provides for standard-layout structs:

  1. The first non-static data member, in declaration order, or a standard-layout struct is at offset 0 ([class.mem.general]/27).
  2. It is possible to define the common initial sequence of two such structs, which is obtained by comparing the members of each struct in declaration order; see [class.mem.general]/23. And when a union contains two or more members whose types are (potentially different) standard-layout structs, there is a limited form of legal type punning between them, as long as you only read from the common initial sequence; see [class.mem.general]/26.

Both of these guarantees rely on the fact that in a standard-layout struct, there is no ambiguity as to which non-static data member is "first". That first non-static data member is located at offset 0, and when comparing this standard-layout struct to another one, it will be compared with the first non-static data member of the other struct to determine whether they belong to the common initial sequence.

If the standard were relaxed to allow class A in your example to be standard-layout, it would have to explain which member is first. As discussed in the comments, implementations have freedom to lay out i before j or j before i. In order to make A standard-layout, the standard would have to take away that freedom from implementations.

Only in the special case where all non-static data members are first declared in the same class, it was judged worthwhile to require all implementations to lay out the class as if all non-static data members were first declared in the most derived class, and all base classes take up no space.

Brian Bi
  • 111,498
  • 10
  • 176
  • 312
0

Directly answering the question as phrased:

The purpose of this bullet is to allow very simple cases of inheritance where only one of the classes has data members.

Data layout for inheritance is unspecified, so the standard could just disallow inheritance altogether, but the standard makes an exception if one class has no data to treat the result still as Standard Layout.

Ryan
  • 14,392
  • 8
  • 62
  • 102
Martin Ba
  • 37,187
  • 33
  • 183
  • 337