Сохранение типа и дальнейшее приведение к нему (?)
Было необходимо реализовать класс-обертку для указателя на функцию (с любым числом любых параметров) для ее дальнейшего вызова. Так же было необходимо, чтобы объекты этого класса можно было помещать в контейнеры, поэтому пришлось избегать использования шаблонного класса, что очень упростило бы все (да и так уже есть std::function).
Вышло как-то так:
class COperation
{
public:
template<typename ReturnType, typename ...Operands>
COperation(ReturnType (*function)(Operands...)) {
m_functionInterface = new Concreate<ReturnType, Operands...>(function);
}
~COperation() {
//delete m_functionInterface; // Почему-то падает
}
public:
template<typename ...Operands>
void execute(Operands... args)
{
std::vector<std::any> argList;
(argList.push_back(args), ...);
m_functionInterface->execute(argList);
}
size_t operandCount() const
{
return m_functionInterface->operandCount();
}
private:
struct Interface
{
virtual ~Interface() = default;
virtual size_t operandCount() const = 0;
virtual void execute(std::vector<std::any>) = 0;
};
template<typename R, typename ...Args>
struct Concreate : Interface
{
explicit Concreate(R (*function)(Args...))
{
m_operandCount = sizeof...(Args);
m_function = function;
}
size_t operandCount() const override
{
return m_operandCount;
}
void execute(std::vector<std::any> argList) override
{
if (argList.size() != sizeof...(Args)) {
throw std::runtime_error("Incorrect number of arguments.");
}
auto applyArgums = [argList](auto&&... args)
{
qsizetype n = 0;
((args = std::any_cast<typeof(args)>(argList.at(n++))), ...);
};
std::apply(applyArgums, m_operands);
result = std::apply(m_function, m_operands);
qDebug() << result;
}
private:
R (*m_function)(Args...);
std::tuple<Args...> m_operands;
qsizetype m_operandCount;
R result;
};
private:
Interface* m_functionInterface;
};
Функции для примера:
inline constexpr qreal addition(qreal left, qreal rigth)
{
return left + rigth;
}
inline constexpr qreal unaryMinus(qreal operand)
{
return -operand;
}
Использование:
COperation a(addition);
COperation um(unaryMinus);
a.execute(10., 20.); // 30
um.execute(10.); // -10
Понятно, что много всего еще можно сделать, но я уже наткнулся на проблему. Из примера видно, что приходится явно передавать параметры нужного типа, так как в противном случае ловим bad any_cast тут.
((args = std::any_cast<typeof(args)>(argList.at(n++))), ...);
Собственно, в этом и есть главная проблема, которую не удается мне решить. В "std::tuple<Args...> m_operands" хранятся аргументы функции "R(*m_function)(Args...);". Нужен механизм, позволяющий преобразовать входные в "execute" параметры к типам из "std::tuple<Args...> m_operands", если это возможно. То есть, например, int мы должны преобразовывать в double (и наоборот), так как это возможно, а не ловить ошибку, как сейчас. Можно ли это как-то реализовать в рамках std::auto? Либо, возможно, как-то переделать функцию "execute"? Из рабочих вариантов было использование std::variant, но мне бы не хотелось ограничиваться типами, которые мы сами указываем. Возможно, у вас будут идеи?)