Передача алгоритма STL другой функции

#c #c 11 #stl

#c #c 11 #stl

Вопрос:

У меня есть вектор определенного пользователем типа (Student). У меня есть 2 функции, которые почти идентичны, за исключением одного вызова функции внутри них.

Вот 2 функции:

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

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

Обе эти функции работают корректно для моего использования, но, похоже, это можно было бы легко построить лучше. Я хочу создать функцию, которую я мог бы передать либо в min_element, либо в max_element, что-то вроде:

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

Но мне не удается заставить это работать должным образом. Я не уверен, как это сделать.

РЕДАКТИРОВАТЬ — вот как я вызываю функцию отправки сообщение об ошибке:

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

Сообщение об ошибке:

 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>amp;, <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>amp;, func)
 Student dispatch(const std::vector<Student> amp;all_students, func){
         ^
m.cpp:71:9: note:   template argument deduction/substitution failed:
 

Комментарии:

1. Не могли бы вы подробнее рассказать о том, как это не работает? Особенно, пожалуйста, покажите, как вы используете свою dispatch функцию.

2. В случае, если вы одновременно вычисляете min и max, примите во внимание std::minmax_element .

Ответ №1:

Это сделает это:

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

Параметр шаблона — это просто тип чего-то.

Я бы посоветовал быть осторожным и никогда не вызывать этот метод с пустым вектором, потому что он вызовет исключение при разыменовании пустого итератора. Лучше было бы:

 template <typename func>
Student dispatch(const std::vector<Student> amp;all_students, const funcamp; fn){
  auto it = fn(std::begin(all_students), std::end(all_students),
      [](const Student amp;a, const Student amp;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.
}
 

Еще одно предложение — отсортировать учащихся перед использованием данных. Тогда вы также сможете получить другие статистические данные, такие как медиана.

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

Самый низкий студент — это первый элемент, а самый высокий — последний. Это также не позволит вам создавать исключения.

Существует еще одна проблема с вашим вызовом. Вам нужно вызвать dispatch, например:

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

STL не выполняет никакой дедуктивной магии и не может самостоятельно решить, какая max_element функция вам нужна. Поэтому вы должны указать его.

Комментарии:

1. Я должен был быть более ясным, я понимаю, что сортировка, вероятно, является лучшим вариантом, однако это всего лишь небольшая демонстрационная программа для работы над изучением C11.

Ответ №2:

std::max_element это шаблонная функция, и компилятор не может определить тип шаблона, необходимый таким образом.

Вы можете использовать следующее, чтобы определить, какой прототип вы хотите:

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

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

Student dispatch(const std::vector<Student> amp;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, amp;std::min_element);
    dispatch(v, amp;std::max_element);
    return 0;
}
 

Живой пример

Ответ №3:

Мой предпочтительный способ сделать это — обернуть алгоритм в лямбда-выражение, а затем передать лямбда-выражение в функцию шаблона. Он имеет приятный синтаксис, когда вы оборачиваете его в макрос:

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

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

// ...

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

Комментарии:

1. Хотя я не поклонник defines, это приятная магия 🙂

2. Это выглядит устрашающе знакомым. При соответствующем оборудовании вы также можете использовать ordered_by (MFLIFT (get_average))) вместо лямбда-выражения.

Ответ №4:

Ваша функция может быть написана так, как вы хотите, следующим образом:

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

И вызывается как

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

Вы можете немного сократить детализацию, если реализуете operator< for Student . Это позволит вам опустить аргумент шаблона для компаратора.

 template<typename Func>
Student dispatch(const std::vector<Student> amp;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>);
 

Еще один способ сделать это — всегда вызывать min_element внутри диспетчеризации, но передавать компараторы с другим поведением.

 template<typename Comparator>
Student dispatch(const std::vector<Student> amp;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
 

Наконец, если вы всегда собираетесь извлекать как самые низкие, так и самые высокие оценки, стандартная библиотека предлагает std::minmax_element , которые будут извлекать оба в одном вызове.

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

Живая демонстрация всех различных опций.