Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Passing STL algorithm to another function

Tags:

c++

c++11

stl

I have a vector of a user defined type (Student). I have 2 functions which are almost identical except for a single function call inside them.

Here is the 2 functions:

Student lowest_grade(const std::vector<Student> &all_students){
  return *std::min_element(std::begin(all_students), std::end(all_students),
      [](const Student &a, const Student &b){
    return a.get_average() < b.get_average();});
}

Student highest_grade(const std::vector<Student> &all_students){
  return *std::max_element(std::begin(all_students), std::end(all_students),
      [](const Student &a, const Student &b){
    return a.get_average() < b.get_average();});
}

Both of these functions are working correctly for my use but it seems like this could easily be constructed better. I want to create a function which I could pass in either min_element or max_element, something like:

template <typename func>
Student dispatch(const std::vector<Student> &all_students, func){
  return *func(std::begin(all_students), std::end(all_students),
      [](const Student &a, const Student &b){
    return a.get_average() < b.get_average();});
}

But I can't manage to get this to work properly. I am not sure how to go about doing this.

EDIT - This is how I am calling the dispatch function + the error message:

std::cout<<"lowest: "<< dispatch(all_students, std::max_element);

The error message is:

g++ m.cpp -std=c++11 -Wall -o main
m.cpp: In function ‘int main()’:
m.cpp:86:63: error: missing template arguments before ‘(’ token
   std::cout<<"lowest: "<< dispatch(all_students, std::function(std::max_element));
                                                               ^
ryan@ryan-VirtualBox:~/Desktop/Prog/daily/167m$ make
g++ m.cpp -std=c++11 -Wall -o main
m.cpp: In function ‘int main()’:
m.cpp:86:81: error: no matching function for call to ‘dispatch(std::vector<Student>&, <unresolved overloaded function type>)’
   std::cout<<"lowest: "<< dispatch<std::function>(all_students, std::max_element);
                                                                                 ^
m.cpp:86:81: note: candidate is:
m.cpp:71:9: note: template<class func> Student dispatch(const std::vector<Student>&, func)
 Student dispatch(const std::vector<Student> &all_students, func){
         ^
m.cpp:71:9: note:   template argument deduction/substitution failed:
like image 691
r-s Avatar asked Jun 27 '14 06:06

r-s


Video Answer


2 Answers

This will do it:

template <typename func>
Student dispatch(const std::vector<Student> &all_students, const func& fn){
  return *fn(std::begin(all_students), std::end(all_students),
      [](const Student &a, const Student &b){
    return a.get_average() < b.get_average();});
}

The template parameter is just a type of something.

I would suggest to be careful to call this method never with an empty vector, because it will raise an exception when dereferencing an empty iterator. Better it would be:

template <typename func>
Student dispatch(const std::vector<Student> &all_students, const func& fn){
  auto it = fn(std::begin(all_students), std::end(all_students),
      [](const Student &a, const Student &b){
    return a.get_average() < b.get_average();});
  if (it != all_students.end()) {
    return *it;
  }
  // Some exception handling, because returning an instance of student is not possible.
}

Another suggestion is to sort the students before using the data. Then you will also be able to get other statistical data like median.

std::sort(all_students.begin(), all_students.end() [](const Student &a, const Student &b){return a.get_average() < b.get_average();});

The lowest student is the first element and the highest the last. This will also prevent you from raising exceptions.

There is another problem with your call. You need to call dispatch like:

dispatch(all_students, std::max_element<std::vector<Student>::const_iterator, std::function<bool(const Student &, const Student &)>>);

The STL does no deduction magic and cannot decide by itself which max_element function your want. So your have to specify it.

like image 81
Stefan Weiser Avatar answered Oct 31 '22 08:10

Stefan Weiser


std::max_element is a template function and compiler cannot deduce the template type needed that way.

You may use the following to force which prototype you want:

// Your lambda as functor
struct CompAverage
{
    bool operator () (const Student & a, const Student & b) const
    {
        return a.get_average() < b.get_average();
    }
};

using Student_IT = std::vector<Student>::const_iterator;

Student dispatch(const std::vector<Student> &all_students,
                 Student_IT (*f)(Student_IT, Student_IT, CompAverage))
{
    return *f(std::begin(all_students), std::end(all_students), CompAverage{});
}

int main()
{
    std::vector<Student> v(42);

    dispatch(v, &std::min_element);
    dispatch(v, &std::max_element);
    return 0;
}

Live example

like image 23
Jarod42 Avatar answered Oct 31 '22 10:10

Jarod42