我正在开发一个相对简单的程序(实际上是一个计算器)。但是,我决定让我的程序的所有组件尽可能通用,因为:
作为该计划的一部分,我正在使用我正在编写的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这个函数应该是Rank-2(如果没有请注释,我将删除标签)。它看起来不对劲。有没有办法做到这一点?
更新
我被建议尝试传递一个带有通用operator ()
的仿函数作为模板参数,但这也不起作用。
答案 0 :(得分:3)
C ++ 14中的常用技巧是使用一些index_sequence
(参见here),然后使用以下内容:
template<typename ... Args, size_t ... I>
auto evaluate(Tuple<Args ...> const& t, index_sequence<I...>)
{
return make_tuple(evaluate(get<I>(t))...);
}
请参阅例如this answer以获取此方法的示例(唯一的区别是此处还调用了一个函数调用)。
因此,您在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
。
答案 1 :(得分:2)
我认为如果没有C ++ 14,你可以做到这一点。我将假设你的Tuple
是如何构建的,即这两个存在的存在:
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'}
Here是一个完整的代码示例,也记录了所有类型。当然,使用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>.