Convert from one class to a related class (not typecasting)

Imagine I have a C++ program that operates a machine that can rearrange the biology of any animal so that it is now of a different type of animal. So a bird can become a dog, a dog can become a cat, etc.

My program stores in memory a bunch of objects of type "Animal", which represent the various animals in my possession. Derived from the "Animal" class are various classes, each one representing a different type of animal (dog, cat, etc.) Once my program causes the conversion of an animal, it must be aware of the animal's new abilities/inabilities & weaknesses. For example, if my bird is converted into a dog, "polly.canFly" should now be false. Or, "polly.fly()" should no longer be valid.
Okay, I have imagined that.
I dealt with this issue before. Check this thread of mine:

http://www.cplusplus.com/forum/general/136276/

JLBorges' suggestion of using the proxy pattern really worked wonders. But the patterns introduced can get really complex, so you need to really study it and be careful (and get ready to do a lot of debugging as your program evolves).
This looks like a job for templates.

If I have access to the Animal class, perhaps I could write a private function like this:
1
2
3
4
5
6
7
template <typename AnimalType>
AnimalType* convert()
{
AnimalType* animal = new AnimalType();
animal->name = this->name;
return animal;
}

Edit: Is there a way to then set animal's memory address to that of the object being converted?
Last edited on
I think I can do what I want just by dropping down to Assembly level. I was thinking of creating a function whose sole purpose is to change the address to which a pointer points.
Why not just use polymorphic behavior with an abstract base class? I don't know how exactly you want to approach this unless I see your program, but as I understand it now you can just use a base pointer and (re)cast it as needed.
> I can do what I want just by dropping down to Assembly level.

Yes, but difficult, error-prone, and hard to maintain.


> I was thinking of creating a function whose sole purpose is to change the address to which a pointer points.

That is the way to go.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
#include <iostream>
#include <string>
#include <typeinfo>
#include <memory>
#include <iomanip>

struct animal
{
    explicit animal( std::string name ) : name_(name) {}

    virtual ~animal() = default ;

    virtual std::string who() const { return typeid(*this).name() ; }

    virtual bool can_fly() const { return false ; }
    virtual bool can_woof() const { return false ; }

    virtual std::string name() const { return name_ ; }

    using pointer = std::unique_ptr<animal> ;

    template < typename T, typename... CONSTRUCTOR_ARGS >
    static pointer make( CONSTRUCTOR_ARGS&&... args )
    { return std::make_unique<T>( std::forward<CONSTRUCTOR_ARGS>(args)... ) ; }

    template < typename T, typename... MORE_CONSTRUCTOR_ARGS >
    static pointer& convert( pointer& p, MORE_CONSTRUCTOR_ARGS&&... args )
    { return p = make<T>( p->animal::name(), std::forward<MORE_CONSTRUCTOR_ARGS>(args)... ) ; }

    private: std::string name_ ;
};

struct cat : animal
{
    cat( std::string name, int age ) : animal(name), age(age) {}

    virtual std::string who() const override { return "cat (age " + std::to_string(age) + ')' ; }

    int age = 1 ;
};

struct dog : animal
{
    dog( std::string name, std::string colour ) : animal(name), colour(colour) {}

    virtual std::string who() const override { return colour + ' ' + "dog" ; }
    virtual bool can_woof() const override { return true ; }

    std::string colour = "grey" ;
};

struct bird : animal
{
    using animal::animal ;
    virtual bool can_fly() const override { return true ; }
};

std::ostream& info( const animal::pointer& pa, std::ostream& stm = std::clog )
{
    static const char* const can_it[] = { " can't ", " can " } ;
    return stm << pa->who() << ' ' << std::quoted( pa->name() )
               << can_it[ pa->can_fly() ] << "fly,"
               << can_it[ pa->can_woof() ] << "woof.\n" ;
}

int main()
{
    auto my_pet = animal::make<bird>( "polly" ) ;
    info(my_pet) << "----------------\n" ;

    animal::convert<cat>( my_pet, 2 ) ; // name remains unchanged
    info(my_pet) << "----------------\n" ;

    my_pet = animal::make<dog>( "fido", "white" ) ; // name changed to "fido"
    info(my_pet) ;
}

4bird "polly" can fly, can't woof.
----------------
cat (age 2) "polly" can't fly, can't woof.
----------------
white dog "fido" can't fly, can woof.

http://coliru.stacked-crooked.com/a/ef16a050fb5be75a
The best and easy way is through polymorphism. You can define a base class as animal and then you can derive other classes like cat, dog, bird, etc. Then when you convert an object of type bird to an object of type dog. Then the specific attributes of the bird will be null and void to the object of type dog.

If you need any further help then don't hesitate to contact me.
Thanks, everyone. I just got around to actually testing out a solution. Here's a full program that works on my machine.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#include <iostream>
using namespace std;
struct Animal
{
    bool canFly = false;
    string name;
    template <typename destType> destType *convert()
    {
        destType *newAnimal = new destType();
        newAnimal->name = this->name;
        memmove(this, newAnimal, sizeof(destType));
        delete newAnimal;
        return reinterpret_cast<destType*>(this);
    }
};

struct Dog : Animal {
    const bool canFly = false;
    void bark()
    {
        cout << "Bark!";
    }
};

struct Bird : Animal {
    bool canFly = true;
};

string yesNoString(bool _bool)
{
    if (_bool) {
        return "yes";
    }
    else
    {
        return "no";
    }
}

int main(int argc, const char * argv[]) {
    // Define our animal.
    Bird *polly = new Bird();
    polly->name = "Polly";
    cout << "\nCan " + polly->name + " fly? " + yesNoString(polly->canFly);
    Dog *pollyDog = polly->convert<Dog>();
    cout << "\nCan " + polly->name + " fly? " + yesNoString(polly->canFly);
    pollyDog->bark();
    return 0;
}



Edit: Just wanted to add: If we didn't have access to the source codes for any of the above classes, we could also have done something like this:
1
2
3
4
5
6
7
8
9
struct AnimalConverter {
 template <typename destType> static destType *convert(Animal *_animal)
    {
        destType *newAnimal = new destType();
        newAnimal->name = _animal->name;
        memmove(_animal, newAnimal, sizeof(destType));
        delete newAnimal;
        return reinterpret_cast<destType*>(this);
    }

Edit #3: It would seem that there is a caveat in my code. After Polly's conversion, she still isn't taking on only one type. She isn't just a Dog, she's also still a Bird. I can still make her able to fly by calling polly->canFly=true; however, pollyDog->canFly=true still fails.
Last edited on
Topic archived. No new replies allowed.