Как заменить динамический полиморфизм статическим?
Допустим, у нас есть несколько классов
class A{
double r(){ return 2.; }
void p(){ std::cout << "Hello!"; }
};
class B{
char r(){ return '2'; }
void p(){ std::cout << "Hello!"; }
};
class C{
int r(){ return 2; }
void p(){ std::cout << "Hello!"; }
};
Для статического полиморфизма обычно (или не обычно) используют std::variant
std::variant<A, B, C> vars[3] = {A{}, B{}, C{}};
Теперь, использую паттерн 'посетитель', мы можем написать лямбда функцию, которая будет вызывать метод p()
auto invoke_p = [](auto& el){
el.p();
};
Теперь мы можем спокойно пройти по всему массиву и 'посетить' каждый вариант:
for(size_t i = 0; i < 3; ++i){
std::visit(invoke_p, vars[i]);
}
Всё кажется простым, пока не нужно получить что-то из варианта.
template <size_t N>
class num{
std::array<size_t, n> value;
};
class A{
num<1> r(){ return {}; }
void p(){ std::cout << "Hello!"; }
};
class B{
num<2> r(){ return {}; }
void p(){ std::cout << "Hello!"; }
};
class C{
num<3> r(){ return {}; }
void p(){ std::cout << "Hello!"; }
};
std::variant<A, B, C> vars[3] = {A{}, B{}, C{}};
size_t sum = 0;
for(size_t i = 0; i < 3; ++i){
auto tmp = std::visit(invoke_p, vars[i]);
for(auto n: tmp){
std::cout << n;
}
}
Тут начинаются проблемы, возвращаемый тип всегда разный, я не знаю, какой должен быть подход, чтобы красиво и грамотно решить эту проблему. Может есть какой-то паттерн или статический полиморфизм подходит только для посетителя. (если вообще я правильно называю это статическим полиморфизмом, если нет, то поправьте и спасибо за понимание, я новичок)
Правка
У меня есть классы: пирамида, призма, тетраэдр и тд. У каждого элемента есть грань, она может быть 3х угольником или 4х угольником. Каждый угол грани это 3d точка.
И вот, к примеру, мой кусок кода, который я написал так-как-смог
static auto get_nodes = [&](auto& el1, auto& el2, auto& el3, auto& el4) {
for (auto f1 : el1.get_m_faces()) {
for (auto f2 : el2.get_m_faces()) {
for (auto f3 : el3.get_m_faces()) {
if(f1 == f2 && f2 == f3){
// очень важная работа
}
}
}
};
Мне не нравится то, что пришлось сделать лямбда-функцию, которая принимает несколько объектов.
Вместо этого я бы хотел что-то такое: (Тут el1, el2, el3 это std::variant)
std::vector<.....> v;
for (size_t i = 0; i < el1.get_m_faces().size(); ++i) {
v.emplace_back(std::visit(получить грань номер i));
// Не знаю, как в std::visit передать номер грани дополнительным аргументом
}
for (size_t i = 0; i < el2.get_m_faces().size(); ++i) {
v.emplace_back(std::visit(получить грань номер i));
}
for (size_t i = 0; i < el3.get_m_faces().size(); ++i) {
v.emplace_back(std::visit(получить грань номер i));
}
// дальше найти одинаковые, так сложность алгоритма будет не O(n^3), а O(3n) с копейками
// Но тогда std vector должен тоже хранить **variant** или **any**
Да и мне вообще интересно, как правильно решать задачи, когда нужно что-то получить из таких структур. Или правильным решением будет - всеми силами этого избегать?
Ответы (1 шт):
с++ это не питон или другой язык с динамической типизацией. Нельзя просто так взять и возвратить что попало и надеяться, что компилятор с этим справится. К сожалению, принцип AAA разбаловал новичков...
Пойдем в строку кода
auto tmp = std::visit(invoke_p, vars[i]);
тут Вы надеетесь, что visit проявит недюжинные способности и возвратит что то, по чему можно итерироваться. А он так не умеет. Совсем. Это не хаскелевский map/fmap. Плюс ко всему, то, что в tmp лежит должно иметь иметь разные типы у каждого элемента. variant? Гетерогенный вектор? есть туплы (std::tuple), но так красиво с ними не выйдет.
Но когда шаблоны пасуют (потому что они не нужны в данном случае), на помощь приходит обычный классический с++. Я набросал чуточку кода, тут конечно явно не хватает get'ров и set'еров со скрытием публичных вещей, и наверно пары функций-удобняшек, что бы сократить наполнение, но это Вам будет виднее, как переделать, а мне идею показать.
#include <iostream>
#include <functional>
#include <vector>
#include <memory>
// храним одну точку. но может использовать glm::vec3
struct Point
{
float x,y,z;
};
//что бы легко было выводить в консоль (например, для отладки)
std::ostream& operator<<(std::ostream& os, const Point& p) {
os << "[" << p.x << "," << p.y << "," << p.z << "]";
return os;
}
// храним грани
struct Face
{
std::string name;
std::vector<Point> points;
};
std::ostream& operator<<(std::ostream& os, const Face& f) {
os << "[";
for (Point p : f.points) {
os << p << " ";
}
os << "]";
return os;
}
struct Shape {
std::vector<Face> faces;
virtual void show() = 0;
};
class Cube: public Shape
{
public:
Cube() {
faces.resize(6);
faces[0].name = "First";
faces[0].points.push_back({0,0,0});
faces[0].points.push_back({0,0,1});
faces[0].points.push_back({0,1,1});
faces[0].points.push_back({0,1,0});
}
void show() override
{
std::cout << "I'm Cube\n";
}
};
class Tetrahedron : public Shape
{
public:
Tetrahedron()
{
faces.resize(4);
}
void show() override
{
std::cout << "I'm Tetrahedron\n";
}
};
int main()
{
// используем shared_ptr, что бы избежать хранения указателей и ручного управления памятью.
std::vector<std::shared_ptr<Shape>> shapes;
shapes.push_back(std::make_shared<Cube>());
shapes.push_back(std::make_shared<Tetrahedron>());
// спросим, кто они
for (auto& sh : shapes) {
sh->show();
std::cout << "I have " << sh->faces.size() << " faces\n";
}
// сделаем пробежку по внутренностям
for (auto& sh : shapes) {
sh->show();
for (auto& f : sh->faces) {
std::cout << f.name << " face\n";
std::cout << f << '\n';
}
}
}
и отвечая на вопрос в заголовке "Как заменить динамический полиморфизм статическим?" - а зачем?