Полиморфные (универсальные) функции как аргументы в переполнении стека

Я разрабатываю относительно простую программу (на самом деле калькулятор). Однако я решил сделать все компоненты моей программы как можно более общими, потому что:

  1. Это хорошая практика.
  2. Это держит вещи интересными.

В рамках этой программы я использую Tuple класс, который я пишу. Я знаю, что класс уже существует, но мне нравится иметь полный контроль над моим кодом, и это только упражнение.

Одна вещь, которую мне нужно сделать, — это преобразовать кортеж выражений (где сами выражения являются общими) в кортеж, содержащий результат вычислений выражений. Короче говоря, у меня есть (с опущенными тривиальными частями):

template <class T>
class Expression {

public:
virtual T Eval() = 0;

// ...
};

template <class First, class ... Rest>
class Tuple {

// ...

private:
First first;
Tuple<Rest ...> rest;
};

И я хотел бы специализироваться на кортеже общего типа, как это:

template <template <class> class R, class First, class ... Rest>
class Tuple<R<First>, R<Rest> ...> {

// and here is the problem:
Tuple<First, Rest ...> Transform(function<template<class T> T(R<T>)>);
};

После чего я мог сделать это:

template <class T> // There has to be a better way to do this
T Eval(Expression<T>& expr){
return expr.Eval();
}

// ...
Tuple<First, Rest ...> tuple = exprs.Transform(Eval);

Здесь есть несколько мест, где я не уверен, как поступить, и будет признателен настоящий эксперт, который мог бы помочь мне здесь. Я ожидаю, что этот код не будет компилироваться из-за незначительных недостатков, но это не главное — мое главное беспокойство — строка, которую я пометил. Если я правильно помню из короткого периода, я узнал, Haskell, эта функция должна иметь ранг 2 (если нет, пожалуйста, прокомментируйте, и я уберу тег). Это просто не выглядит правильно. Есть какой-либо способ сделать это?

Обновить:

Мне посоветовали попробовать передать функтор с универсальным operator () в качестве аргумента шаблона, но это тоже не сработало.

6

Решение

Я думаю, что вы можете сделать это довольно просто без C ++ 14. Я собираюсь предположить несколько вещей о том, как ваш Tuple построен, а именно, что эти два ctors существуют:

Tuple(First, Rest... );                // (1)
Tuple(First, const Tuple<Rest...>& );  // (2)

Нам нужна одна особенность типов: учитывая функцию, которую мы преобразуем, нам нужно знать, какие типы она производит:

template <typename T, typename F>
using apply_t = decltype(std::declval<F>()(std::declval<T>()));

(Боже, я люблю C ++ 11)

С этим мы можем легко определить тип возвращаемого значения, и это просто вопрос рекурсивного вызова функции:

template <typename First, typename... Rest>
struct Tuple
{
template <typename F>
Tuple<apply_t<First, F>, apply_t<Rest, F>...>
Transform(F func)
{
return {func(first), rest.Transform(func)}; // hence the need
// for ctor (2)
};
};

(В зависимости от того, как вы написали свой Tuple вам может понадобиться или не понадобиться базовый случай для тривиального преобразования, которое просто возвращает Tuple<>или базовый случай, который просто возвращает Tuple<apply_t<First, F>>, В любом случае, ничего страшного).

И вам даже не нужно специализироваться Tuple совсем. Вам просто нужно передать правильные функторы. Например:

struct Zero
{
template <typename T>
int operator()(T ) { return 0; }
};

struct Incr
{
template <typename T>
T operator()(T x) { return x + 1; }
};

Tuple<int, double, char> tup(1, 2.0, 'c');
auto z = tup.Transform(Zero{}); // z is Tuple<int, int, int>{0, 0, 0}
auto i = tup.Transform(Incr{}); // i is Tuple<int, double, char>{2, 3.0, 'd'}

ВотЭто полный пример кода, ведение журнала всех типов тоже. Конечно, с C ++ 14 мы можем сделать это встроенным:

auto i2 = tup.Transfom([](auto x) -> decltype(x) {return x+1; });
// i2 is a Tuple<int, double, char>{2, 3.0, 'd'};
// without the trailing decltype, it gets deduced as Tuple<int, double, int>.
2

Другие решения

Обычный трюк в C ++ 14 — это использовать некоторые index_sequence (увидеть Вот) а потом что-то вроде:

template<typename ... Args, size_t ... I>
auto evaluate(Tuple<Args ...> const& t, index_sequence<I...>)
{
return make_tuple(evaluate(get<I>(t))...);
}

Смотрите, например, этот ответ для примера этого подхода (единственное отличие состоит в том, что здесь дополнительно вызывается вызов функции).

Итак, что вам нужно здесь, в вашем Tuple класс для этого:

  • Реализация кастома get функция, которая ведет себя аналогично std::getто есть принимает аргументы индекса вариации.
  • Реализация кастома make_tuple функция, которая ведет себя аналогично std::make_tuple и создает кортеж из списка через запятую.

Далее вам нужен шаблон функции evaluate который может оценить одно выражение, но я думаю, у вас уже есть это.


РЕДАКТИРОВАТЬ: Я просто понял, что вышесказанное может быть не очень полезно для вас. Скорее следует отметить, что вы можете сделать это также рекурсивно:

template<typename ... Args>
auto evaluate(Tuple<Args ...> const& t)
{
return tuple_cat(make_tuple(evaluate(t.first)), evaluate(t.rest));
}

template<typename T> auto evaluate(Tuple<T> const& t) { return evaluate(t.first); }

Опять же, вам требуется make_tuple функция, конкатенатор кортежей tuple_cat и оценщик с одним выражением evaluate,

3

По вопросам рекламы [email protected]