9

The following code computes the average of a particular property of T in the items collection:

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;
}

I can then call this with a lambda expression:

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

How would I go about doing this in c++? Here's what I have so far:

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();
}

How might I go about calling this with a c++ lambda?


Edit: Thank you all very much, here's what I ended up with:

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();
}

And in 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;
});
aligray
  • 2,812
  • 4
  • 26
  • 35

4 Answers4

14

The equivalent thing would be a std::function<double(T)>. This is a polymorphic function object which may accept any function object that has the correct operator(), including lambdas, hand-written functors, bound member functions, and function pointers, and enforces the correct signature.

Do not forget that in C#, the Func interface (or class, I forget) is written as Func<T, Result>, whereas C++'s std::function is written as result(T).

Oh, in addition, if you don't have C++0x, there's one in TR1 and also one in Boost, so you should easily be able to access it.

Puppy
  • 144,682
  • 38
  • 256
  • 465
  • 1
    +1. This is the best way to enforce the constraints on the parameter and the return type while at the same type accepting both function pointers and functors. Perhaps you could post the code corresponding to your solution. – Luc Touraille Jun 10 '11 at 12:36
4

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();
}

You can use anything callable and copyable as a selector.

Konstantin Oznobihin
  • 5,234
  • 24
  • 31
  • +1 for exposing the "usual" way of doing this sort of thing: use a template parameter so that the function can accept either function pointer or functors. However, the constraints on the return type and the parameter type of the selector are less clearly defined than when using `function` (from boost or tr1). – Luc Touraille Jun 10 '11 at 12:35
  • 1
    You can easily add necessary constraints using static_assert, so there is no much difference regarding this. At the same time, function adds some performance and memory overhead which could be unreasonable in some cases. – Konstantin Oznobihin Jun 10 '11 at 13:06
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();
}

Note that this isn't the best way, it's just the easiest to write and understand. The proper way is to use a functor so it works with C++0x's lambda expressions too.

Blindy
  • 65,249
  • 10
  • 91
  • 131
  • @Blindy: Can you elaborate on the functor part? – Daniel Hilgarth Jun 10 '11 at 12:23
  • Well if you use boost, it's as easy as using `boost::function selector`, it supports passing in pointers to static functions (like what I used), as well as functor objects (classes with `operator()`, which include lambda expressions). – Blindy Jun 10 '11 at 12:25
  • I downvoted this. Do not recommend anyone to use a function pointer, because they're terrible- but in addition, a function pointer is *not* equivalent to the polymorphic `Func` type in C#. – Puppy Jun 10 '11 at 12:28
  • I upvoted this because of DeadMG's unfair downvote. It does not have to be an exact equivalent because there is never one unless they map to the same runtime. – alternative Jun 12 '11 at 22:58
3

There are many ways, depending what you want to do, how generic and re-usable the solution should be, what memory constraints you are OK with (i.e. continuously laid out memory vs some linked lists etc). But here is a simple example using 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;
}

It could be much easier assuming, for example, that you can accept only high level containers (not arrays). And you can get lambdas by throwing in C++0x, for example:

#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;
}

The choice is yours :-)

P.S.: Yeah, and that std::function (which is actually stuff from Boost in pre-C++0x) can be used to make your algorithm non-template. Otherwise it only limits you in terms of what "selector" you can pass in.

Luc Touraille
  • 79,925
  • 15
  • 92
  • 137
  • 3
    In your last example, if you want to accept arrays as well (i.e. accept all "ranges"), you should use `std::begin(T)`/`std::end(T)` instead of accessing the iterators directly. (Those functions can be found in Boost too.) – Luc Touraille Jun 10 '11 at 12:45
  • I see that you edited your answer to use `begin(T)` and `end(T)`, but you still call `empty()` on the items, which will not work with arrays. You can test for emptiness by comparing begin and end iterators. – Luc Touraille Jun 10 '11 at 15:13
  • @Luc Touraille: And there is also `size ()`. I guess either calculating number of iterations or using `distance` if applicable will help. But I am kinda lazy to write fully blown solution. Feel free to help if you want to. –  Jun 10 '11 at 15:17