#опытным
Допустим, у вас есть вектор тасок и вам нужно выполнить каждую из них и поместить результаты выполнения в другой вектор:
struct Task {
int execute() {
return 42;
}
};
std::vector<Task> tasks(10);
std::vector<int> results;Примерно так это может выглядеть в суперупрощенном виде.
Эту задачу легко решить с помощью цикла:
results.reserve(tasks.size());
for (auto& task: tasks) {
result.emplace_back(task.execute());
}
И дело в шляпе.
Однако cppcoreguidelines нам говорят:
Use standard algorithms where appropriate, instead of writing some own implementation.Может ли мы здесь использовать стандартные алгоритмы? Да, конечно можем. С рэнджами это очень легко:
std::vector results = tasks |
std::views::transform(&Task::execute) |
std::ranges::to<std::vector>();
Мы уже говорили в этом посте, что алгоритмы диапазонов обязаны использовать std::invoke под капотом, поэтому std::views::transform легко переварит указатель на метод.
Но что, если вы живете в эру до С++20? У вас есть стандартный std::transform, однако он уже не такой умный и не умеет принимать указатели на методы.
std::transform(tasks.begin(), tasks.end(),
std::back_inserter(results), &Task::execute); // Not working!
Какие варианты у нас есть в такой ситуации?
❗️ std::function
std::transform(tasks.begin(), tasks.end(),
std::back_inserter(results), std::function<int(Task&)>(&Task::execute));
Works, but at what costs?
std::function в данной ситуации - это бить по воробьям ракетами. std::function обычно работает сильно медленнее, чем прямой вызов. Поэтому давайте посмотрим на других кандидатов.
👍 Использовать лямбду
std::transform(tasks.begin(), tasks.end(),
std::back_inserter(results), [](auto& input) { return input.execute(); });
Это работает, но приходится городить огород вокруг execute. Лямбды всегда вносят некоторый "шум" в код из-за чего его сложнее воспринимать. Поэтому это не самый идеальный вариант. Есть что-то получше?
✅ std::mem_fn. Спонсор сегодняшней передачи. std::mem_fn принимает указатель на метод и возвращает тонкую обертку над ним, которая условно позволяет вызывать методы класса не так
(x.*&Item::Foo)(), а вот так: (&Item::Foo)(x). То есть позволяет унифицировать синтаксис вызова указателя на метод класса с синтаксисом вызова обычной функции. С помощью std::mem_fn наш код трансформации выглядит вот так:std::transform(tasks.begin(), tasks.end(),
std::back_inserter(results), std::mem_fn(&Task::execute));
Минимум кода вокруг execute без потери производительности. Кайф!
Отличная функция, которая позволит вашему коду быть выразительным и быстрым.
Express yourself. Stay cool.
#cppcore #cpp20 #STL