В моем приложении мне нужно абстрагировать шаблонный алгоритм, чтобы его можно было выбирать во время выполнения. Я вроде хочу «шаблонные виртуальные функции» (где и ifce, и impl создаются во время компиляции со списком разрешенных типов), но я понимаю, что не могу шаблонировать виртуальные функции в C ++. У меня было несколько идей на этом пути, и это лучшее, что я придумал до сих пор - но я действительно не доволен тем, насколько это сложно. Я пропускаю что-то более очевидное? Мне нужно, чтобы решение было быстрым, а также производным от общего базового класса.
#include <memory>
#include <iostream>
enum class calc_enum { calc_1, calc_2, calc_3 };
// implementation class
template<typename F, typename T, typename... U> struct algo_impl;
// base class
struct algo_base {};
// interface class
template<typename... U> struct algo_ifce;
template<>
struct algo_ifce<> : algo_base
{
template<typename F, typename T>
algo_ifce(std::shared_ptr<algo_impl<F, T>>&& ptr)
: ptr_{ std::move(ptr) }
{}
template<typename V>
V calc_ifce(V val) {
throw std::runtime_error("nope");
}
std::shared_ptr<void> ptr_;
};
template<typename U, typename... REST>
struct algo_ifce<U, REST...> : algo_ifce<REST...>
{
using fn_type = U(*)(void*, U);
template<typename F, typename T>
algo_ifce(std::shared_ptr<algo_impl<F, T, U, REST...>>&& ptr_)
: algo_ifce<REST...>(std::shared_ptr<algo_impl<F, T, REST...>>(std::move(ptr_))), fn_(algo_impl<F, T, U, REST...>::calc_static)
{}
template<typename V>
V calc_ifce(V val) {
if constexpr (std::is_same_v<U, V>) return calc_dispatch(val);
else return algo_ifce<REST...>::template calc_ifce<V>(val);
}
U calc_dispatch(U val) {
return fn_(algo_ifce<REST...>::ptr_.get(), val);
}
fn_type fn_;
};
template<typename F, typename T>
struct algo_impl<F, T>
{
algo_impl(T data)
: data_{ data }
{}
T data_;
};
template<typename F, typename T, typename U, typename... REST>
struct algo_impl<F, T, U, REST...> : algo_impl<F, T, REST...>
{
algo_impl(T data)
: algo_impl<F, T, REST...>{ data }
{}
static U calc_static(void* ptr, U val) {
return static_cast<algo_impl*>(ptr)->calc_algo(val);
}
U calc_algo(U val) {
return F::template calc<U>(val, algo_impl<F, T, REST...>::data_);
}
};
// algo types
template<typename T>
struct calc_impl_1
{
template<typename U> static U calc(U val, T data) {
std::cout << "algo 1, T:" << typeid(T).name() << ", U:" << typeid(U).name() << std::endl;
return static_cast<U>(data) + val;
}
};
template<typename T>
struct calc_impl_2
{
template<typename U> static U calc(U val, T data) {
std::cout << "algo 2, T:" << typeid(T).name() << ", U:" << typeid(U).name() << std::endl;
return static_cast<U>(data) * val;
}
};
template<typename T>
struct calc_impl_3
{
template<typename U> static U calc(U val, T data) {
std::cout << "algo 3, T:" << typeid(T).name() << ", U:" << typeid(U).name() << std::endl;
return static_cast<U>(data) - val;
}
};
template<typename T, typename... U>
std::shared_ptr<algo_ifce<U...>> make_algo(calc_enum e, T data)
{
switch (e) {
case calc_enum::calc_1: return std::make_shared<algo_ifce<U...>>(std::make_shared<algo_impl<calc_impl_1<T>, T, U...>>(data)); break;
case calc_enum::calc_2: return std::make_shared<algo_ifce<U...>>(std::make_shared<algo_impl<calc_impl_2<T>, T, U...>>(data)); break;
case calc_enum::calc_3: return std::make_shared<algo_ifce<U...>>(std::make_shared<algo_impl<calc_impl_3<T>, T, U...>>(data)); break;
default: throw std::runtime_error("unrecognised algo");
}
}
template<typename T, typename... U>
struct algo_container
{
algo_container(calc_enum algo, T data)
: algo_{ make_algo<T, U...>(algo, data) }
{}
template<typename V> V calc(V val) {
return algo_->template calc_ifce<V>(val);
}
std::shared_ptr<algo_ifce<U...>> algo_;
};
template<typename T>
struct my_num
{
my_num(T t)
: t_{ t }
{}
operator T() const { return t_; }
T t_;
};
template<typename T> my_num(T)->my_num<T>;
void test_algo_c()
{
// data type types of calc supported runtime algo data
// v------------v v---------------------------v v---------------v v--v
algo_container<double, double, float, my_num<double> > a1(calc_enum::calc_1, 1.0);
algo_container<float, double, float, my_num<double> > a2(calc_enum::calc_2, 1.0f);
algo_container<my_num<double>, double, float, my_num<double> > a3(calc_enum::calc_3, 1.0);
a1.calc(2.0f); // supported output: algo 1, T : double, U : float
a1.calc(3.0); // supported output: algo 1, T : double, U : double
a1.calc(my_num(-1.0)); // supported output: algo 1, T : double, U : struct my_num<double>
a2.calc(2.0f); // supported output: algo 2, T : float, U : float
a2.calc(2.0); // supported output: algo 2, T : float, U : double
a2.calc(my_num(-2.0)); // supported output: algo 2, T : float, U : struct my_num<double>
//a2.calc(5); // crash, no int version... output: n/a
a3.calc(2.0f); // supported output: algo 3, T : struct my_num<double>, U : float
a3.calc(2.0); // supported output: algo 3, T : struct my_num<double>, U : double
a3.calc(my_num(1.5)); // supported output: algo 3, T : struct my_num<double>, U : struct my_num<double>
}
Обновление
Я изменил исходный код, чтобы иметь стек рекурсивного интерфейса классы, которые содержат виртуальную функцию и функцию отправки stati c для выбора правильной виртуальной функции. Затем у меня есть стек рекурсивных классов реализации, которые реализуют функцию. Это работает очень хорошо и имеет только одну виртуальную функцию (вроде того, о чем я говорил в начале). Также теперь он не компилируется, если тип недоступен (вместо исключения раньше). Я до сих пор не могу не чувствовать, что есть лучший способ сделать это ...
#include <memory>
#include <iostream>
enum class calc_enum { calc_1, calc_2, calc_3 };
// interface class
template<typename... U> struct algo_ifce;
template<>
struct algo_ifce<>
{};
template<typename U, typename... REST>
struct algo_ifce<U, REST...> : algo_ifce<REST...>
{
template<typename V>
V calc_ifce(V val) {
if constexpr (std::is_same_v<U, V>) return calc_dispatch(val);
else return algo_ifce<REST...>::template calc_ifce<V>(val);
}
virtual U calc_dispatch(U val) = 0;
};
template<typename F, typename I, typename T, typename... U>
struct algo_impl;
template<typename F, typename I, typename T>
struct algo_impl<F, I, T> : I
{
algo_impl(T data) : data_{ data } {}
T data_;
};
template<typename F, typename I, typename T, typename U, typename... REST>
struct algo_impl<F, I, T, U, REST...> : algo_impl<F, I, T, REST...>
{
algo_impl(T data) : algo_impl<F, I, T, REST...>{ data } {}
virtual U calc_dispatch(U val) override { return F::template calc<U>(val, algo_impl<F, I, T, REST...>::data_); }
};
// algo types
template<typename T>
struct calc_impl_1
{
template<typename U> static U calc(U val, T data) {
std::cout << "algo 1, T:" << typeid(T).name() << ", U:" << typeid(U).name() << std::endl;
return static_cast<U>(data) + val;
}
};
template<typename T>
struct calc_impl_2
{
template<typename U> static U calc(U val, T data) {
std::cout << "algo 2, T:" << typeid(T).name() << ", U:" << typeid(U).name() << std::endl;
return static_cast<U>(data) * val;
}
};
template<typename T>
struct calc_impl_3
{
template<typename U> static U calc(U val, T data) {
std::cout << "algo 3, T:" << typeid(T).name() << ", U:" << typeid(U).name() << std::endl;
return static_cast<U>(data) - val;
}
};
template<typename T, typename... U>
std::unique_ptr<algo_ifce<U...>> make_algo(calc_enum e, T data)
{
switch (e) {
case calc_enum::calc_1: return std::make_unique<algo_impl<calc_impl_1<T>, algo_ifce<U...>, T, U...>>(data); break;
case calc_enum::calc_2: return std::make_unique<algo_impl<calc_impl_2<T>, algo_ifce<U...>, T, U...>>(data); break;
case calc_enum::calc_3: return std::make_unique<algo_impl<calc_impl_3<T>, algo_ifce<U...>, T, U...>>(data); break;
default: throw std::runtime_error("unrecognised algo");
}
}
template<typename T, typename... U>
struct algo_container
{
algo_container(calc_enum algo, T data)
: algo_{ make_algo<T, U...>(algo, data) }
{}
template<typename V> V calc(V val) {
return algo_->template calc_ifce<V>(val);
}
std::unique_ptr<algo_ifce<U...>> algo_;
};
template<typename T>
struct my_num
{
my_num(T t)
: t_{ t }
{}
operator T() const { return t_; }
T t_;
};
template<typename T> my_num(T)->my_num<T>;
void test_algo_d()
{
// data type types of calc supported runtime algo data
// v------------v v---------------------------v v---------------v v--v
algo_container<double, double, float, my_num<double> > a1(calc_enum::calc_1, 1.0);
algo_container<float, double, float, my_num<double> > a2(calc_enum::calc_2, 1.0f);
algo_container<my_num<double>, double, float, my_num<double> > a3(calc_enum::calc_3, 1.0);
a1.calc(2.0f); // supported output: algo 1, T : double, U : float
a1.calc(3.0); // supported output: algo 1, T : double, U : double
a1.calc(my_num(-1.0)); // supported output: algo 1, T : double, U : struct my_num<double>
a2.calc(2.0f); // supported output: algo 2, T : float, U : float
a2.calc(2.0); // supported output: algo 2, T : float, U : double
a2.calc(my_num(-2.0)); // supported output: algo 2, T : float, U : struct my_num<double>
//a2.calc(5); // doesn't compile as wanted...
a3.calc(2.0f); // supported output: algo 3, T : struct my_num<double>, U :float
a3.calc(2.0); // supported output: algo 3, T : struct my_num<double>, U :double
a3.calc(my_num(1.5)); // supported output: algo 3, T : struct my_num<double>, U :struct my_num<double>
}