7

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:
2
  • Can you please elaborate on how it's not working? Especially please show how you use your dispatch function. Commented Jun 27, 2014 at 6:51
  • In case you're computing both min and max at the same time, take std::minmax_element into account. Commented Jun 27, 2014 at 9:32

4 Answers 4

3

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.

Sign up to request clarification or add additional context in comments.

1 Comment

I should have been clearer, I realize that the sort is likely the best option, however this is just a small demo program for working on learning C11.
2

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

Comments

2

My preferred way to do this is to wrap the algorithm into a lambda and then pass the lambda to the template function. It has nice syntax when you wrap it in a macro:

#define LIFT(...)                                                  \
    ([](auto&&... args) -> decltype(auto) {                        \
        return __VA_ARGS__(std::forward<decltype(args)>(args)...); \
    })

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

// ...

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

2 Comments

Although I am not a fan of defines, this is some nice magic :)
This looks eerily familiar. With the appropriate machinery, you can also use ordered_by (MFLIFT (get_average))) in lieu of the lambda.
1

Your function can be written the way you want as follows:

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

And invoked as

dispatch(students, 
         std::min_element<decltype(students)::const_iterator, 
                          bool(*)(const Student&, const Student&)>);
dispatch(students, 
         std::max_element<decltype(students)::const_iterator, 
                          bool(*)(const Student&, const Student&)>);

You can cut down on the verbosity quite a bit if you implement operator< for Student. This will allow you to omit the template argument for the comparator.

template<typename Func>
Student dispatch(const std::vector<Student> &all_students, Func func)
{
    assert(!all_students.empty());
    return *func(std::begin(all_students), std::end(all_students));
}

dispatch(students, 
         std::min_element<decltype(students)::const_iterator>);
dispatch(students, 
         std::max_element<decltype(students)::const_iterator>);

Yet another way to do this, is to always call min_element within dispatch, but pass in comparators with different behaviors.

template<typename Comparator>
Student dispatch(const std::vector<Student> &all_students, Comparator comp)
{
    assert(!all_students.empty());
    return *std::min_element(std::begin(all_students), std::end(all_students), 
                             comp);
}

dispatch(students, std::less<Student>());
dispatch(students, std::greater<Student>());  // requires operator> for Student

Finally, if you're always going to fetch both the lowest and the highest grades, the standard library offers std::minmax_element that will fetch both in a single call.

auto minmax = std::minmax_element(std::begin(students), std::end(students));

Live demo of all the different options.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.