Polymorphic(Generic)函数作为C ++中的参数

时间:2014-11-24 16:17:04

标签: c++ generics variadic-templates higher-kinded-types higher-rank-types

我正在开发一个相对简单的程序(实际上是一个计算器)。但是,我决定让我的程序的所有组件尽可能通用,因为:

  1. 这是一种很好的做法。
  2. 让事情变得有趣。
  3. 作为该计划的一部分,我正在使用我正在编写的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 ()的仿函数作为模板参数,但这也不起作用。

2 个答案:

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