Consider the following declarations of a pair of related structs. The descendant class adds no member variables, and the only member function is a constructor that does nothing but forward all its arguments to the base class's constructor.
struct Base {
Base(int a, char const* b):
a(a), b(b)
{ }
int a;
char const* b;
};
struct Descendant: Base {
Descendant(int a, char const* b):
Base(a, b)
{ }
};
Now consider the following code using those types. Function foo
expects to receive an array of Base
. However, main
defines an array of Descendant
and passes that to foo
instead.
void foo(Base* array, unsigned len)
{
/* <access contents of array> */
}
int main()
{
Descendant main_array[] = {
Descendant(0, "zero"),
Descendant(1, "one")
};
foo(main_array, 2);
return 0;
}
Is behavior of this program defined? Does the answer depend on the body of foo
, like whether it writes to the array or only reads from it?
If sizeof(Derived)
is unequal to sizeof(Base)
, then behavior is undefined according to the answers to a previous question about a base pointer to an array of derived objects. Is there any chance the objects in this question will have differing sizes, though?
Is behavior of this program defined? Does the answer depend on the body of foo, like whether it writes to the array or only reads from it?
I'm gonna hazard an answer saying that the program is well defined (as long as foo
is) even if it is written in another language (e.g. C).
If
sizeof(Derived)
is unequal tosizeof(Base)
, then behavior is undefined according to the answers to a previous question about a base pointer to an array of derived objects. Is there any chance the objects in this question will have differing sizes, though?
I don't think so. According to my reading of the standard(*) §9.2 clause 17
Two standard-layout struct (Clause 9) types are layout-compatible if they have the same number of non-static data members and corresponding non-static data members (in declaration order) have layout-compatible types (3.9).
§9 clauses 7 through 9 detail the requirements for layout-compability:
7 A standard-layout class is a class that:
has no non-static data members of type non-standard-layout class (or array of such types) or reference,
has no virtual functions (10.3) and no virtual base classes (10.1),
has the same access control (Clause 11) for all non-static data members,
has no non-standard-layout base classes,
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
has no base classes of the same type as the first non-static data member.
8 A standard-layout struct is a standard-layout class defined with the class-key
struct
or the class-keyclass
. A standard-layout union is a standard-layout class defined with the class-keyunion
.9 [ Note: Standard-layout classes are useful for communicating with code written in other programming languages. Their layout is specified in 9.2. — end note ]
Note especially the last clause (combined with §3.9) - according to my reading this is guaranteeing that as long as you're not adding too much "C++ stuff" (virtual functions etc. and thus violating the standard-layout requirement) your structs/classes will behave as C structs with added syntactical sugar.
Would anyone have doubted the legality if Base
didn't have a constructor? I don't think so as that pattern (deriving from a C structure adding a constructor/helper functions) is idiomatic.
I'm open to the possibility that I'm wrong and welcome additions/corrections.
(*) I'm actually looking at N3290 here, but the actual standard should be close enough.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With