0

Why doesn't ths "cross-cast" work?

The following creates this object inheritance model:

     FOOD
    /    \
 CHEESE  CAKE

Here I attempt to static_cast a pointer from cheese to cake, making a cheesecake :D. I am getting the following error from Apple Clang/LLVM:

 ERROR: Static cast from 'Cheese *' to 'Cake *', which are not related by inheritance, is not allowed

But they ARE related by inheritance: they are siblings.

Why can't I cast a cheese to a cake, like I cast an int to a float? Why can't I "cross-cast" from derived objects which inherit from the same base class?

Minimal, complete, and verifiable example follows, with static_cast and dynamic_cast attempts included, to highlight errors.

#include <iostream>
#include <string>

class Food {
public:
    Food(std::string brand):brand_(brand) {}
    virtual ~Food() {};
    std::string brand() { return brand_; }
    virtual void cut()const { std::cout << "Food cut." << std::endl; }
    void eat()const { std::cout << "Food eaten." << std::endl; }
private:
    std::string brand_;
};

class Cheese : public Food {
public:
    Cheese(std::string brand):Food(brand) {};
    virtual void cut()const { std::cout << "Cheese cut.\n"; }
    void eat()const { std::cout << "Cheese eaten.\n"; }
};

class Cake : public Food {
public:
    Cake(std::string brand):Food(brand) {};
    virtual void cut()const { std::cout << "Cake cut.\n"; }
    void eat()const { std::cout << "Cake eaten.\n"; }
};

int main() {
    Food f("tbd");
    Cheese c("Cheddar");
    Cake cc("Cheesecake");
    Food * food_ptr;
    Cheese *cheese_ptr, *cheeseCake_ptr;
    Cake *cake_ptr;

    food_ptr = &f;
    food_ptr->cut();  //-> "Food cut."

    food_ptr = &c;
    food_ptr->cut();  //-> "Cheese cut." Result of virtual keyword.

    cheese_ptr = dynamic_cast<Cheese*> (food_ptr);
    cheese_ptr->cut(); //-> "Cheese Cut." The downcast worked

    food_ptr = &cc;
    cake_ptr = dynamic_cast<Cake*> (food_ptr);
    cake_ptr->cut(); //-> "Cake Cut." pointer reassignment and downcast worked.


    cheeseCake_ptr = dynamic_cast<Cheese*> (food_ptr);
    cheeseCake_ptr->cut(); //-> "Cake cut." Again, Food* dynamically casted to Cheese*

    /* ~~~ NOTE: THE FOLLOWING EXAMLES INTENTIONALLY THROW ERRORS ~~~ */

    /*
     Dynamic cross-cast attempt:
     ERROR: Assigning to 'Cheese *' from incompatable type 'Cake *'
     Dynamic cast: doensn't make sense, as the relationshiop between cheese and cake is not polymorphic
    */

    cheeseCake_ptr = dynamic_cast<Cake*> (cheese_ptr);
    cheeseCake_ptr->cut();

    /*
     Static cross-cast attempt:
     ERROR: Static cast from 'Cheese *' to 'Cake *', which are not related by inheritance, is not allowed
     Static cast: why doesn't this work? We know the data types.
     */

    cheese_ptr = &c;
    cake_ptr = &cc;
    cheeseCake_ptr = static_cast<Cake*> (cheese_ptr);

    std::cout << "\n\n";
    return 0;
}
kmiklas
  • 13,085
  • 22
  • 67
  • 103

2 Answers2

1

not related by inheritance would mean on the same branch...

You should not mistake casting values for casting pointers (or the converse), they are different type conversion.

The int to float uses a function to convert a value of a given type to a value of the target type (note that this function can be explicit or implicit, it is implicit for that particular case ie. defined in the langage). In C++ you can implement this on custom class types by implementing a ctor for class A that is able to received a value of type B, or by implementing a cast operator for class B that returns a value of type A.

The Cake * to Cheese * do not convert values, but tries to view a given object in a different manner. It is sub-typing polymorphism (so you need to use dynamic_cast). Given your type hierarchy, a Cheese object can easily be viewed (not converted) as a Food object by using a Food pointer to point to a Cheese object (just like you can be viewed differently by different persons, (think the way friend, professor or doctor can apprehend you while you are always the same person!). It is clear (at least in your hierarchy) that a Cheese can't be viewed as a Cake, if it would be the case how the Cheese object would react to Cake's specific methods? You are an Animal, a Dog also, but it is difficult to imagine that I can view you as a Dog (you don't have 4 legs isn't it?...

Don't be fooled by your case. Ok it seems that the contents of Cheese and Cake are the same, but the given hierarchy tell the truth: there are not on the same branch, you can't get a Cheese pointer from a Cake pointer. C++ compiler protects you against this.

Now things change slightly if you use multiple inheritance like a CheeseCake inheriting from Cheese and Cake. Then given a CheeseCake object viewed through a Cheese pointer, it is possible to convert it to Cake pointer because a CheeseCake is both! Of course even in that multiple inheritance scenario, a Cheese object cannot be viewed as a Cake through pointers.

Jean-Baptiste Yunès
  • 34,548
  • 4
  • 48
  • 69
  • But does not the same logic hold true for casting values? Let's say we convert a float to an int. Now, can we truncate the decimal on an int? Retrieve the fractional part, find its ceiling or floor? No! None of these make sense after a cast from float to int... but we still do it. – kmiklas Jun 28 '17 at 21:25
  • I told you, when casting value, you *transform* type to another type. With pointer there is no transformation! In a value casting, you have two values, the original one and the transformed one through the conversion function. In pointer casting there is always one object, but two views on it (a pointer is a view on an object), there is no transformation at all (I can see you as OP and some of your friends see you as soccer player, same person, two different views, there is no value transformation, only different ways to interact with you). – Jean-Baptiste Yunès Jun 29 '17 at 05:41
0

The error says it all, ERROR: Static cast from 'Cheese *' to 'Cake *', which are not related by inheritance, is not allowed. Even though they are siblings, they are not related through inheritance.

If you absolutely want to make cheesecake, you can do multiple inheritance.

    FOOD
   /    \
CHEESE   CAKE
   \     /
  CHEESECAKE

Like this:

class CheeseCake : public Cake, public Cheese
Kent
  • 996
  • 6
  • 10
  • 3
    But then it's twice the calories because the inheritance is not virtual :( – Quentin Jun 28 '17 at 19:21
  • I don't want to make cheescake by combining the two; I want to call a cheesecake a ``cake`` object instead of a ``cheese`` object. A bit of a confusing example, I realize in retrospect. – kmiklas Jun 28 '17 at 19:46