ЗАДАЧА:
Я бы хотел добиться динамического полиморфизма с типом < (т.е. время выполнения вызова функции) на несвязанных типах - то есть на типах, которые не имеют общий базовый класс. Мне кажется, что это достижимо или, по крайней мере, теоретически звучит. Я попытаюсь более четко определить свою проблему.
ОПРЕДЕЛЕНИЕ ПРОБЛЕМЫ:
Учитывая следующее:
- два или более несвязанных типа
A1, ..., An
, каждый из которых имеет метод с именемf
, возможно с разными сигнатурами, но с тем же типом возвратаR
; и - a
boost::variant<A1*, ..., An*>
объектv
(или любой другой тип варианта), который может и должен принимать в любое время одно значение любого из этих типов;
Моя цель - написать инструкции, концептуально эквивалентные v.f(arg_1, ..., arg_m);
, которые будут иметь отправлено во время выполнения для функции Ai::f
, если фактический тип значения, содержащегося в v
, равен Ai
. Если аргументы вызова несовместимы с формальными параметрами каждой функции Ai
, компилятор должен вызвать ошибку.
Конечно, мне не нужно придерживаться синтаксиса v.f(arg_1, ..., arg_m)
: например, что-то вроде call(v, f, ...)
также приемлемо.
Я пытался добиться этого на С++, но до сих пор мне не удалось найти решение хорошего (у меня есть куча плохих). Ниже я уточняю, что я имею в виду под "хорошим решением".
ТРУДНОСТИ:
A хорошее решение - это все, что позволяет мне подражать идиоме v.f(...)
, например. call_on_variant(v, f, ...);
и удовлетворяет следующим ограничениям:
- не требует какой-либо отдельной декларации для каждой функции
f
, которая должна быть вызвана таким образом (например,ENABLE_CALL_ON_VARIANT(f)
) или для любого списка несвязанных типовA1, ..., An
, которые могут быть обработаны полиморфно (например,ENABLE_VARIANT_CALL(A1, ..., An)
) где-то еще в коде, особенно в глобальной области; - не требует, чтобы явно указывал типы входных аргументов при выполнении вызова (например,
call_on_variant<int, double, string>(v, f, ...)
). Именование типа возврата в порядке, поэтому допустимоcall_on_variant<void>(v, f, ...)
.
Следуйте демонстративному примеру, который, надеюсь, уточнит мое желание и требования.
Пример:
struct A1 { void f(int, double, string) { cout << "A"; } };
struct A2 { void f(int, double, string) { cout << "B"; } };
struct A3 { void f(int, double, string) { cout << "C"; } };
using V = boost::variant<A1, A2, A3>;
// Do not want anything like the following here:
// ENABLE_VARIANT_CALL(foo, <whatever>)
int main()
{
A a;
B b;
C c;
V v = &a;
call_on_variant(v, f, 42, 3.14, "hello");
// Do not want anything like the following here:
// call_on_variant<int, double, string>(v, f, 42, 3.14, "hello");
V v = &b;
call_on_variant(v, f, 42, 3.14, "hello");
V v = &c;
call_on_variant(v, f, 42, 3.14, "hello");
}
Выход этой программы должен быть: ABC
.
ЛУЧШЕЕ (НЕИСПРАВНО) ПОПЫТКА:
Самое близкое, что я получил к желаемому решению, - это макрос:
#define call_on_variant(R, v, f, ...) \
[&] () -> R { \
struct caller : public boost::static_visitor<void> \
{ \
template<typename T> \
R operator () (T* pObj) \
{ \
pObj->f(__VA_ARGS__); \
} \
}; \
caller c; \
return v.apply_visitor(c); \
}();
Что будет работать отлично, , если в локальных классах допустимы только члены шаблона (см. этот вопрос). У кого-нибудь есть идея, как это исправить, или предложить альтернативный подход?