C++中作为参数的多态(通用)函数

6
我正在开发一个相对简单的程序(实际上是一个计算器)。然而,我决定尽可能使我的程序组件通用,因为:
1.这是一个好的习惯。 2.它使事情变得有趣。
作为这个程序的一部分,我正在使用我编写的一个“元组”类。我知道已经存在一个类,但我喜欢完全控制我的代码,这只是一个练习。
我需要做的一件事是将表达式的元组(其中表达式本身是通用的)转换为包含表达式求值结果的元组。简而言之,我有以下内容(略去了微不足道的部分):
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 () 的函子作为模板参数传递,但那也行不通。


尝试使用 boost::mpl::transform - n. m.
@n.m. - 这不提供我正在寻找的功能。 - user2008934
@dyp - 是的,我知道,那是一个纯虚函数。但问题不在于此。问题是 - 我如何将通用函数作为参数传递? - user2008934
@dyp - 是的,我会编辑那个。 - user2008934
2个回答

3

在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


std::get和std::make_tuple的等效实现很容易,我已经在其他场景中实现了它们,只是略有不同。但我仍然不知道如何编写Transform函数,这是重要的部分。 - user2008934
这个怎么是通用的?我不知道如何在类似的情况下扩展它(比如一个由不同类型列表组成的元组 -> 每个列表的总和组成的元组) - 似乎你必须为每种情况重复编写此代码。 - user2008934
@user2008934:你将一个元组传递给 evaluate 函数,然后返回一个包含转换类型的元组。在这个例子中,我传递了一个 Tuple<int,int>,应用了一个函数 double evaluate(int),并且会得到一个 Tuple<double,double>(如果有 tuple_cat 的话——实际上它是一个 Tuple<double, Tuple<double>>)。这里的通用性在于:对于任何类型和任何评估函数,该代码都可以正常工作 - davidhigh
但是每次你都必须编写一个新的 evaluate 函数。如果我想把不同类型的列表元组转换成它们的总和元组,该怎么办?你将不得不再次编写所有内容,但用 sum 替换 evaluate!我希望能够将 sum 作为参数传递并完成它。 - user2008934
让我们在聊天中继续这个讨论:http://chat.stackoverflow.com/rooms/65541/discussion-between-user2008934-and-davidhigh。 - user2008934
显示剩余3条评论

2

我认为你可以完全不使用C++14来轻松完成这个任务。我将假设一些关于你的如何构造的事情,即存在以下两个构造函数:

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>.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接