aboutsummaryrefslogtreecommitdiff
path: root/tests/test_tagbased_polymorphic.cpp
blob: 12ba6532f362c152c2dbfe7087afe46aeb609ee2 (plain)
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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
/*
    tests/test_tagbased_polymorphic.cpp -- test of polymorphic_type_hook

    Copyright (c) 2018 Hudson River Trading LLC <opensource@hudson-trading.com>

    All rights reserved. Use of this source code is governed by a
    BSD-style license that can be found in the LICENSE file.
*/

#include <pybind11/stl.h>

#include "pybind11_tests.h"

struct Animal {
    // Make this type also a "standard" polymorphic type, to confirm that
    // specializing polymorphic_type_hook using enable_if_t still works
    // (https://github.com/pybind/pybind11/pull/2016/).
    virtual ~Animal() = default;

    // Enum for tag-based polymorphism.
    enum class Kind {
        Unknown = 0,
        Dog = 100,
        Labrador,
        Chihuahua,
        LastDog = 199,
        Cat = 200,
        Panther,
        LastCat = 299
    };
    static const std::type_info *type_of_kind(Kind kind);
    static std::string name_of_kind(Kind kind);

    const Kind kind;
    const std::string name;

protected:
    Animal(const std::string &_name, Kind _kind) : kind(_kind), name(_name) {}
};

struct Dog : Animal {
    explicit Dog(const std::string &_name, Kind _kind = Kind::Dog) : Animal(_name, _kind) {}
    std::string bark() const { return name_of_kind(kind) + " " + name + " goes " + sound; }
    std::string sound = "WOOF!";
};

struct Labrador : Dog {
    explicit Labrador(const std::string &_name, int _excitement = 9001)
        : Dog(_name, Kind::Labrador), excitement(_excitement) {}
    int excitement;
};

struct Chihuahua : Dog {
    explicit Chihuahua(const std::string &_name) : Dog(_name, Kind::Chihuahua) {
        sound = "iyiyiyiyiyi";
    }
    std::string bark() const { return Dog::bark() + " and runs in circles"; }
};

struct Cat : Animal {
    explicit Cat(const std::string &_name, Kind _kind = Kind::Cat) : Animal(_name, _kind) {}
    std::string purr() const { return "mrowr"; }
};

struct Panther : Cat {
    explicit Panther(const std::string &_name) : Cat(_name, Kind::Panther) {}
    std::string purr() const { return "mrrrRRRRRR"; }
};

std::vector<std::unique_ptr<Animal>> create_zoo() {
    std::vector<std::unique_ptr<Animal>> ret;
    ret.emplace_back(new Labrador("Fido", 15000));

    // simulate some new type of Dog that the Python bindings
    // haven't been updated for; it should still be considered
    // a Dog, not just an Animal.
    ret.emplace_back(new Dog("Ginger", Dog::Kind(150)));

    ret.emplace_back(new Chihuahua("Hertzl"));
    ret.emplace_back(new Cat("Tiger", Cat::Kind::Cat));
    ret.emplace_back(new Panther("Leo"));
    return ret;
}

const std::type_info *Animal::type_of_kind(Kind kind) {
    switch (kind) {
        case Kind::Unknown:
        case Kind::Dog:
            break;

        case Kind::Labrador:
            return &typeid(Labrador);
        case Kind::Chihuahua:
            return &typeid(Chihuahua);

        case Kind::LastDog:
        case Kind::Cat:
            break;
        case Kind::Panther:
            return &typeid(Panther);
        case Kind::LastCat:
            break;
    }

    if (kind >= Kind::Dog && kind <= Kind::LastDog) {
        return &typeid(Dog);
    }
    if (kind >= Kind::Cat && kind <= Kind::LastCat) {
        return &typeid(Cat);
    }
    return nullptr;
}

std::string Animal::name_of_kind(Kind kind) {
    std::string raw_name = type_of_kind(kind)->name();
    py::detail::clean_type_id(raw_name);
    return raw_name;
}

namespace PYBIND11_NAMESPACE {
template <typename itype>
struct polymorphic_type_hook<itype, detail::enable_if_t<std::is_base_of<Animal, itype>::value>> {
    static const void *get(const itype *src, const std::type_info *&type) {
        type = src ? Animal::type_of_kind(src->kind) : nullptr;
        return src;
    }
};
} // namespace PYBIND11_NAMESPACE

TEST_SUBMODULE(tagbased_polymorphic, m) {
    py::class_<Animal>(m, "Animal").def_readonly("name", &Animal::name);
    py::class_<Dog, Animal>(m, "Dog")
        .def(py::init<std::string>())
        .def_readwrite("sound", &Dog::sound)
        .def("bark", &Dog::bark);
    py::class_<Labrador, Dog>(m, "Labrador")
        .def(py::init<std::string, int>(), "name"_a, "excitement"_a = 9001)
        .def_readwrite("excitement", &Labrador::excitement);
    py::class_<Chihuahua, Dog>(m, "Chihuahua")
        .def(py::init<std::string>())
        .def("bark", &Chihuahua::bark);
    py::class_<Cat, Animal>(m, "Cat").def(py::init<std::string>()).def("purr", &Cat::purr);
    py::class_<Panther, Cat>(m, "Panther")
        .def(py::init<std::string>())
        .def("purr", &Panther::purr);
    m.def("create_zoo", &create_zoo);
};