Здравствуйте, x-code, Вы писали:
XC>То есть, в общем и целом — нужна какая-то конструкция типа большого switch, которая бы позволяла явно вызвать правильную специализацию шаблонного кода в зависимости от значения некой переменной времени выполнения.
http://en.cppreference.com/w/cpp/language/typeid
Здравствуйте, x-code, Вы писали:
XC>Иными словами, как называется операция, обратная type erasure, как это обычно делают и делают ли вообще? Какие идиомы существуют для этого?
Полиморфизм времени исполнения это называется. И мультиметоды.
1. Если есть некоторый шаблон функции,
template<class T> void foo(T x)
{
...
bar(x);
buz(x);
xyz<T>(); // не зависит от наличия x
...
T y; // не зависит от наличия x
T z(x);
...
}
то его можно перетащить в рантайм следующими способами
— ограничить T классами, сделать старое доброе ООП
void foo(IFoo const& x)
{
...
x->bar();
buz(x);
xyz(x); // вынужденно зависит от наличия x
...
shared_ptr<IFoo> y(x->Create()); // вынужденно зависит от наличия x
z(x->Clone());
...
}
— вынести функции работы с типом наружу, сделать словарь методов
void foo(void* x, IFooTraits const* xtraits)
{
...
xtraits->bar(x);
buz(x, xtraits);
xyz(xtraits); // словарь можно передавать отдельно
...
shared_ptr<void> y = xtraits->Create(); // не зависит от наличия x
shared_ptr<void> z = xtraits->Clone(x);
...
}
2. Специализации шаблонов и перегрузки функций — если они не расползаются — можно организовать через переопределение словаря методов
template<class T> void foo(T);
template<class E> void foo(vector<E>);
void foo(int);
template<class T> void bar() { T x; foo(x); }
превращается в
struct IBarTraits
{
virtual shared_ptr<void> Create() const = 0;
virtual void foo(shared_ptr<void> x) const = 0;
};
template<class T>
struct GeneralBarTraits : IBarTraits { ..... };
template<class E>
struct VectorBarTraits : IBarTraits { ..... };
struct IntBarTraits : IBarTraits { ..... };
void bar(IBarTraits* traits) { shared_ptr<void> x = traits->Create(); traits->foo(x); }
3. Если таких семейств функций много, и они расползаются, можно пойти дальше: сделать множественное наследование типов и трейтсов, dynamic_cast'ить их к нужным интерфейсам.
struct ITraits { virtual ~ITraits() {} }; // просто корень иерархии с виртуальными функциями
struct ICtorTraits : ITraits { ..... };
struct IFooTratis : ITraits { ..... };
struct IBarTraits : ITraits { ..... };
.....
void foo(ITraits& t)
{
shared_ptr<void> x = dynamic_cast<ICtorTraits&>(t).Create();
dynamic_cast<IFooTraits&>(t).foo_nullary();
dynamic_cast<IFooTraits&>(t).foo_unary(x);
}
4. Подводя итоги, могу сказать следующее. Всё это добро уже давно изобретено. Посмотри на реализации COM (ATL, Comet) и на boost::any.
То, что я написал выше, — это просто для понимания, как оно устроено. В COM трейтсам соответствуют фабрики классов, например.
Здравствуйте, x-code, Вы писали:
XC>Дальше, пользователь что-то щелкает мышью, и нам нужно произвести обратное действие — восстановить из строки тип, опять перейти к шаблонному коду. Например, по щелчку на некотором поле нужно узнать что там был за тип и вызвать шаблонную функцию, специализированную для этого типа, которая что-то сделает.
Помимо variant'а (который нужно рассматривать в первую очередь) можно сделать такое type-erasure, которое будет запоминать фиксированный набор операций для значений стираемых типов. Например на базе
Boost.TypeErasure:
LIVE DEMO
BOOST_TYPE_ERASURE_FREE((has_action), action, 1)
using any_with_action = any<mpl::vector<copy_constructible<>, has_action<void(_self&)>>>;
struct Foo{};
struct Bar{};
void action(Foo&)
{
cout << "Foo" << endl;
}
void action(Bar&)
{
cout << "Bar" << endl;
}
int main()
{
any_with_action x = Foo{}, y = Bar{};
action(x); // prints "Foo"
action(y); // prints "Bar"
}