3

Here is some code that illustrates the question:

#include <iostream>

class Base {
};

class Derived : public Base {
};

void doThings(Base* bases[], int length)
{
    for (int i = 0; i < length; ++i)
        std::cout << "Do ALL the things\n";
}

int main(int argc, const char * argv[])
{
    Derived* arrayOfDerived[2] = { new Derived(), new Derived() };
    doThings(arrayOfDerived, 2); // Candidate function not viable: no known conversion from 'Derived *[2]' to 'Base **' for 1st argument

    // Attempts to work out the correct cast
    Derived** deriveds = &arrayOfDerived[0];
    Base** bases = dynamic_cast<Base**>(deriveds); // 'Base *' is not a class

    Base** bases = dynamic_cast<Base**>(arrayOfDerived); // 'Base *' is not a class

    // Just pretend that it should work
    doThings(reinterpret_cast<Base**>(arrayOfDerived), 2);

    return 0;
}

Clang produces the errors given in the comments. The question is: "Is there a correct way to cast arrayOfDerived to something that doThings can take?

Bonus marks:

  1. Why does clang produce the errors "'Base *' is not a class" on the given lines? I know that Base* isn't a class it's a pointer. What is the error trying to tell me? Why has dynamic_cast been designed so that in dynamic_cast<T*> the thing T must be a class?
  2. What are the dangers of using the reinterpret_cast to force everything to work?

Thanks as always :)

Ben Whale
  • 493
  • 2
  • 9
  • 1
    Array is not covariance in C++, `dynamic_cast` only take pointer to complete class, and it doesn't make sense to `dynamic_cast` derived class to based class, don't do `reinterpret_cast` like that it is undefined behavior. – yngccc Jul 16 '14 at 16:38
  • 1
    The conversion from `Derived*` to `Base*` is inaccessible because it's a private baseclass. Further, for dynamic_cast to work, you need at least one virtual function. These changes don't matter for your issue though. – Ulrich Eckhardt Jul 16 '14 at 16:54
  • @UlrichEckhardt Sorry! I meant that the inheritance be public. I'll update the question. – Ben Whale Jul 17 '14 at 02:37
  • I don't think this is a duplicate. An array (pointer) is very different from a `std::list`. – Justin Jun 11 '20 at 17:18

3 Answers3

2

No. What you’re asking for here is a covariant array type, which is not a feature of C++.

The risk with reinterpret_cast, or a C-style cast, is that while this will work for simple types, it will fail miserably if you use multiple or virtual inheritance, and may also break in the presence of virtual functions (depending on the implementation). Why? Because in those cases a static_cast or dynamic_cast may actually change the pointer value. That is, given

class A {
  int a;
};

class B {
  string s;
};

class C : public A, B {
  double f[4];
};

C *ptr = new C();

unless the classes are empty, ptr == (A *)ptr and/or ptr == (B *)ptr will be false. It only takes a moment to work out why that must be the case; if you're using single inheritance and there is no vtable, it’s guaranteed that the layout of a subclass is the same as the layout of the superclass, followed by member variables defined in the subclass. If, however, you have multiple inheritance, the compiler must choose to lay the class out in some order or other; I'm not sure what the C++ standard has to say about it (you could check - it might define or constrain the layout order somehow), but whatever order it picks, it should be apparent that one of the casts must result in a change the pointer value.

al45tair
  • 4,405
  • 23
  • 30
2

The first error in your code is that you are using a syntax that should be avoided:

void doThings(Base* bases[], int length)

if you look at the error message, it tells you that bases is actually a Base**, and that is also what you should write if you really want to pass such a pointer.

The second problem has to do with type safety. Imagine this code, where I reduced the array of pointers to a single pointer:

class Base {...};
class Derived1: public Base {...};
class Derived2: public Base {...};
Derived1* p1 = 0;
Base*& pb = p1; // reference to a pointer
pb = new Derived2(); // implicit conversion Derived -> Base

If this code compiled, p1 would now suddenly point to a Derived2! The important difference to a simple conversion of a pointer-to-derived to a pointer-to-base is that we have a reference here, and the pointer in your case is no different.

You can use two variations that work:

Base* pb = p1; // pointer value conversion
Base* const& pb = p1; // reference-to-const pointer

Applied to your code, this involves copying the array of pointers-to-derived to an array of pointers-to-base, which is probably what you want to avoid. Unfortunately, the C++ type system doesn't provide any different means to achieve what you want directly. I'm not exactly sure about the reason, but I think that at least according to the standard pointers can have different sizes.

There are two things I would consider:

  1. Convert doThings() to a function template taking two iterators. This follows the spirit of the STL and allows calls with different pointer types or others that provide an iterator interface.
  2. Just write a loop. If the body of the loop is large, you could also consider extracting just that as a function.
Ulrich Eckhardt
  • 16,572
  • 3
  • 28
  • 55
2

In answer to your main question, the answer is not really, because there's no way of reasonably implementing it. There's no guarantee that the actual addresses in the Base* will be the same as those in the Derived*, and in fact, there are many cases where they aren't. In the case of multiple inheritance, it's impossible that both bases have the same address as the derived, because they must have different addresses from each other. If you have an array of Derived*, whether it be std::vector<Derived*> or a Derived** pointing to the first element of an array, the only way to get an array of Base* is by copying: using std::vector<Derived*>, this would look something like:

std::vector<Base*> vectBase( vectDerived.size() );
std::transform(
    vectDerived.cbegin(),
    vectDerived.cend(),
    vectBase.begin(),
    []( Derived* ptr ) { return static_cast<Base*>( ptr ); } );

(You can do exactly the same thing with Derived** and Base**, but you'll have to tweek it with the known lengths.)

As for your "bonus" questions:

  1. Clang (an I suspect every other compiler in existance), produces the error Base* is not a class because it isn't a class. You're trying to dynamic_cast between Base** and Derived**, the pointed to types are Base* and Derived*, and dynamic_cast requires the pointed to types to be classes.

  2. The only danger with reinterpret_cast is that it won't work. You'll get undefined behavior, which will possibly work in a few simple cases, but won't work generally. You'll end up with something that the compiler thinks is a Base*, but which doesn't physically point to a Base*.

James Kanze
  • 150,581
  • 18
  • 184
  • 329