Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Container covariance in C++

Tags:

I know that C++ doesn't support covariance for containers elements, as in Java or C#. So the following code probably is undefined behavior:

#include <vector> struct A {}; struct B : A {}; std::vector<B*> test; std::vector<A*>* foo = reinterpret_cast<std::vector<A*>*>(&test); 

Not surprisingly, I received downvotes when suggesting this a solution to another question.

But what part of the C++ standard exactly tells me that this will result in undefined behavior? It's guaranteed that both std::vector<A*> and std::vector<B*> store their pointers in a continguous block of memory. It's also guaranteed that sizeof(A*) == sizeof(B*). Finally, A* a = new B is perfectly legal.

So what bad spirits in the standard did I conjure (except style)?

like image 389
Daniel Gehriger Avatar asked Jan 26 '11 17:01

Daniel Gehriger


1 Answers

The rule violated here is documented in C++03 3.10/15 [basic.lval], which specifies what is referred to informally as the "strict aliasing rule"

If a program attempts to access the stored value of an object through an lvalue of other than one of the following types the behavior is undefined:

  • the dynamic type of the object,

  • a cv-qualified version of the dynamic type of the object,

  • a type that is the signed or unsigned type corresponding to the dynamic type of the object,

  • a type that is the signed or unsigned type corresponding to a cv-qualified version of the dynamic type of the object,

  • an aggregate or union type that includes one of the aforementioned types among its members (including, recursively, a member of a subaggregate or contained union),

  • a type that is a (possibly cv-qualified) base class type of the dynamic type of the object,

  • a char or unsigned char type.

In short, given an object, you are only allowed to access that object via an expression that has one of the types in the list. For a class-type object that has no base classes, like std::vector<T>, basically you are limited to the types named in the first, second, and last bullets.

std::vector<Base*> and std::vector<Derived*> are entirely unrelated types and you can't use an object of type std::vector<Base*> as if it were a std::vector<Derived*>. The compiler could do all sorts of things if you violate this rule, including:

  • perform different optimizations on one than on the other, or

  • lay out the internal members of one differently, or

  • perform optimizations assuming that a std::vector<Base*>* can never refer to the same object as a std::vector<Derived*>*

  • use runtime checks to ensure that you aren't violating the strict aliasing rule

[It might also do none of these things and it might "work," but there's no guarantee that it will "work" and if you change compilers or compiler versions or compilation settings, it might all stop "working." I use the scare-quotes for a reason here. :-)]

Even if you just had a Base*[N] you could not use that array as if it were a Derived*[N] (though in that case, the use would probably be safer, where "safer" means "still undefined but less likely to get you into trouble).

like image 102
James McNellis Avatar answered Oct 27 '22 11:10

James McNellis