У меня есть ряд несвязанных типов, которые поддерживают одни и те же операции через перегруженные свободные функции (ad hoc polymorphism):
struct A {};
void use(int x) { std::cout << "int = " << x << std::endl; }
void use(const std::string& x) { std::cout << "string = " << x << std::endl; }
void use(const A&) { std::cout << "class A" << std::endl; }
Как следует из названия вопроса, я хочу хранить экземпляры этих типов в гетерогенном контейнере, чтобы я мог use()
их независимо от конкретного типа. Контейнер должен иметь семантику значений (т.е. Назначение между двумя контейнерами копирует данные, оно не делится им).
std::vector<???> items;
items.emplace_back(3);
items.emplace_back(std::string{ "hello" });
items.emplace_back(A{});
for (const auto& item: items)
use(item);
// or better yet
use(items);
И, конечно, это должно быть полностью расширяемо. Подумайте о библиотечном API, который принимает vector<???>
и клиентский код, который добавляет свои собственные типы к уже известным.
Обычное решение - хранить (умные) указатели на (абстрактный) интерфейс (например, vector<unique_ptr<IUsable>>
), но у него есть ряд недостатков - от верхней части головы:
- Мне нужно перенести мою текущую стратегическую полиморфную модель в иерархию классов, где каждый отдельный класс наследуется от общего интерфейса. О, хвати! Теперь мне приходится писать обертки для
int
иstring
, а что нет... Не говоря уже о уменьшении возможности повторного использования/компоновки из-за того, что свободные функции-члены тесно связаны с интерфейсом (виртуальные функции-члены). - Контейнер теряет семантику значения: простое присваивание
vec1 = vec2
невозможно, если мы используемunique_ptr
(заставляя меня вручную выполнять глубокие копии), или оба контейнера заканчиваются с общим состоянием, если мы используемshared_ptr
(который имеет свои преимущества и недостатки - но поскольку я хочу семантику ценности на контейнере, снова я вынужден вручную выполнять глубокие копии). - Чтобы иметь возможность выполнять глубокие копии, интерфейс должен поддерживать виртуальную функцию
clone()
, которая должна быть реализована в каждом отдельном производном классе. Можете ли вы серьезно подумать о чем-то более скучном, чем это?
Подводя итог:, это добавляет много ненужной связи и требует тонны (возможно, бесполезного) шаблона. Это определенно не удовлетворительно, но пока это единственное практическое решение, о котором я знаю.
Я искал жизнеспособную альтернативу полиморфизму подтипов (наследование интерфейса) на века. Я много играю с специальным полиморфизмом (иначе перегруженные свободные функции), но я всегда сталкиваюсь с одной и той же жесткой стеной: контейнеры должны быть однородными, поэтому я всегда неохотно возвращаюсь к наследованию и умным указателям со всеми недостатками, перечисленными выше ( и, вероятно, больше).
В идеале я хотел бы иметь простой vector<IUsable>
с правильной семантикой значений, не меняя ничего на мою текущую иерархию типов (отсутствия) и сохраняя ad hoc-полиморфизм вместо требуемого полиморфизма подтипа.
Возможно ли это? Если да, то как?