Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When S is a trivial subclass of T, is it safe to use an array of S where an array of T is expected?

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?

like image 363
Rob Kennedy Avatar asked Nov 07 '13 18:11

Rob Kennedy


1 Answers

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 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?

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-key class. A standard-layout union is a standard-layout class defined with the class-key union.

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.

like image 142
user786653 Avatar answered Nov 07 '22 20:11

user786653