【问题标题】:C++ equivalent of C#'s Func<T, TResult>C++ 等效于 C# 的 Func<T, TResult>
【发布时间】:2011-06-10 12:19:05
【问题描述】:

以下代码计算T 集合中T 的特定属性的平均值:

public double Average<T>(IList<T> items, Func<T, double> selector)
{
    double average = 0.0;

    for (int i = 0; i < items.Count; i++)
    {
        average += selector(items[i])
    }

    return average / items.Count;
}

然后我可以使用 lambda 表达式调用它:

double average = Average(items, p => p.PropertyName);

我将如何在 C++ 中执行此操作?到目前为止,这是我所拥有的:

template <typename T>
double average(const vector<T>& items, ?)
{
    double average= 0.0;

    for (int i = 0; i < items.size(); i++)
    {
        average += ?;
    }

    return average/ items.size();
}

我该如何使用 c++ lambda 调用它?


编辑:非常感谢大家,这就是我最终得到的结果:

template <typename T>
double average(const vector<T>& items, function<double(T)> selector)
{
    double result = 0.0;

    for (auto i = items.begin(); i != items.end(); i++)
    {
        result += selector(*i);
    }

    return result / items.size();
}

main():

zombie z1;
z1.hit_points = 10;

zombie z2;
z2.hit_points = 5;

items.push_back(z1);
items.push_back(z2);

double average = average<zombie>(items, [](const zombie& z) {       
    return z.hit_points;
});

【问题讨论】:

    标签: c# c++ c++11 function-object


    【解决方案1】:

    等价的东西是std::function&lt;double(T)&gt;。这是一个多态函数对象,它可以接受任何具有正确 operator() 的函数对象,包括 lambda、手写函子、绑定成员函数和函数指针,并且强制执行正确的签名。

    不要忘记,在 C# 中,Func 接口(或类,我忘记了)写为 Func&lt;T, Result&gt;,而 C++ 的 std::function 写为 result(T)

    哦,另外,如果你没有C++0x,TR1里有一个,Boost里也有一个,所以你应该可以很容易地访问它。

    【讨论】:

    • +1。这是在接受函数指针和函子的同一类型时强制执行对参数和返回类型的约束的最佳方式。也许您可以发布与您的解决方案相对应的代码。
    【解决方案2】:
    template <typename T, typename Selector> double average(const vector<T>& items, Selector selector) { double average= 0.0; for (int i = 0; i < items.size(); i++) { average += selector(items[i]); } return average / items.size(); }

    您可以使用任何可调用和可复制的东西作为选择器。

    【讨论】:

    • +1 用于公开执行此类事情的“通常”方式:使用模板参数,以便函数可以接受函数指针或仿函数。但是,选择器的返回类型和参数类型的约束没有使用function(来自 boost 或 tr1)时明确定义。
    • 您可以使用 static_assert 轻松添加必要的约束,因此在这方面没有太大区别。同时,函数会增加一些性能和内存开销,这在某些情况下可能是不合理的。
    【解决方案3】:
    template <typename T>
    double average(const vector<T>& items, double(*selector)(T))
    {
        double average= 0.0;
    
        for (int i = 0; i < items.size(); i++)
        {
            average += selector(items[i]);
        }
    
        return average/ items.size();
    }
    

    请注意,这不是最好的方法,它只是最容易编写和理解的方法。正确的方法是使用仿函数,这样它也可以与 C++0x 的 lambda 表达式一起使用。

    【讨论】:

    • @Blindy:你能详细说明一下仿函数部分吗?
    • 如果你使用 boost,它就像使用 boost::function&lt;double(T)&gt; selector 一样简单,它支持传递指向静态函数的指针(就像我使用的那样),以及函子对象(带有 operator() 的类,其中包括 lambda 表达式)。
    • 我对此投了反对票。不建议任何人使用函数指针,因为它们很糟糕——但此外,函数指针等同于 C# 中的多态 Func 类型。
    • 由于 DeadMG 的不公平投票,我对此表示赞成。它不必是完全等价的,因为除非它们映射到相同的运行时,否则永远不会有一个。
    【解决方案4】:

    有很多方法,具体取决于您想要做什么、解决方案的通用性和可重用性、您可以接受的内存限制(即连续布局内存与一些链表等)。但这里是一个使用 C++03 的简单示例:

    #include <vector>
    #include <list>
    #include <iostream>
    
    template <typename Iter, typename Selector>
    inline double
    average (Iter it, Iter end, Selector selector)
    {
        double average = 0.0;
        size_t size = 0;
    
        while (it != end)
        {
            average += selector (*it);
            ++size;
            ++it;
        }
    
        return size != 0 ? average / size : 0.0;
    }
    
    struct DoublingSelector
    {
        template <typename T>
        inline T operator () (const T & item)
        {
            return item * 2;
        }
    };
    
    int main ()
    {
        std::vector<int> data;
        data.push_back (1);
        data.push_back (3);
        data.push_back (5);
        std::cout << "The average is: " <<
            average (data.begin (), data.end (),
                DoublingSelector ()) << std::endl;
    }
    

    例如,假设您只能接受高级容器(而不是数组),这可能会容易得多。你可以通过抛出 C++0x 来获得 lambdas,例如:

    #include <vector>
    #include <iterator>
    #include <iostream>
    
    template <typename C, typename Selector>
    inline double
    average (const C & items, Selector selector)
    {
        auto it = std::begin(items);
        auto end = std::end(items);
    
        // Average is only meaningful on a non-empty set of items
        assert(it != end);
    
        double sum = 0.0;
        size_t size = 0;    
    
        for (; it != end; ++it)
        {
            sum += selector(*it);
            ++size;
        }
    
        return sum / size;
    }
    
    int main ()
    {
        std::vector<int> data = { 1, 3, 5 };
        std::cout << "The average is: " <<
            average (data, [] (int v) { return v * 2; }) << std::endl;
    }
    

    选择权在你手中 :-)

    P.S.:是的,std::function(实际上是 C++0x 之前的 Boost 中的东西)可用于使您的算法成为非模板。否则,它只会限制您可以传入的“选择器”。

    【讨论】:

    • 在你的最后一个例子中,如果你也想接受数组(即接受所有“范围”),你应该使用 std::begin(T)/std::end(T) 而不是直接访问迭代器。 (这些功能也可以在 Boost 中找到。)
    • 我看到您编辑了答案以使用 begin(T)end(T),但您仍然在项目上调用 empty(),这不适用于数组。您可以通过比较开始和结束迭代器来测试是否为空。
    • @Luc Touraille:还有size ()。我猜计算迭代次数或使用distance(如果适用)会有所帮助。但我有点懒得写完全成熟的解决方案。如果您愿意,请随时提供帮助。
    猜你喜欢
    • 1970-01-01
    • 2013-11-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多