Поэтому мне часто нужно выполнить какой-нибудь алгоритм STL для ряда элементов, и вместо функции сравнения я хотел бы передать унарную функцию f.
Например, я хотел бы написать что-то вроде этого
std::max_element(begin(range), end(range), f);
чтобы найти максимальный элемент в диапазоне после применения f. Мой текущий обходной путь выглядит примерно так:
std::max_element(begin(range), end(range, [&f](auto a, auto b){ return f(a) < f(b); });
На первый взгляд может показаться, что это не проблема. Но f может быть самим лямбда-выражением или чем-то более сложным, чем просто f. У меня есть две проблемы с этим фрагментом кода: а) Это подвержено ошибкам, потому что можно случайно написать f (a) < f (a) (особенно если оно более сложное и используется копия и прошлое). Это проблема дублирования кода б) Это не очень хорошо выражает намерение. Если я хочу отсортировать по функции, я не хочу иметь дело со сравнением.
К сожалению, я не нашел хорошего решения для такого рода проблем в стандартной библиотеке (или даже в boost), поэтому я хотел бы спросить вас, каково ваше решение этой проблемы. Есть ли причина отсутствия этой перегрузки в алгоритмах?
В стандартной библиотеке это невозможно с использованием существующих функций. Вам нужна комбинация std::transform
и max_element
. Для этого вы можете написать очень простую шаблонную функцию-оболочку. Это не сложно. 3 строки кода. . .
Спасибо за эту идею! Я до сих пор не вижу, как избежать проблемы. Приведенный там пример также имеет проблему дублирования кода.
Стандартные библиотечные функции не могут удовлетворить все возможные требования. Всегда будут моменты, когда вам нужно что-то немного другое. Вы всегда можете написать свою собственную функцию для удовлетворения ваших конкретных потребностей. Вы по-прежнему можете использовать стандартную библиотеку для выполнения основной части этой задачи.
Это определенно правда. Однако, по крайней мере для меня, я никогда не использовал какую-либо другую функцию сравнения, кроме сравнения значения вывода унарной функции. Есть ли способ перегрузить все алгоритмы STL функцией сравнения одной собственной функцией?
Отвечает ли это на ваш вопрос? Требуется очень общая функция argmax на C++
К сожалению, не совсем. Это решает проблему только для определенного алгоритма. Максимум - это просто пример, я бы хотел, чтобы такое поведение звучало для каждого алгоритма.
Используя диапазоны С++ 20, вы можете сделать:
std::ranges::max_element(range | std::views::transform(f));
Почему бы не передать проекцию (3-й аргумент) в max_element?
Я думаю, что в конце концов это то же самое, но да, это тоже сработает max_element(range, {}, f);
Лично я бы предпочел первую версию, потому что вызов для преобразования самостоятельно документирует код, а вторая версия непонятна, если вы с ней не знакомы.
К сожалению, в настоящее время у меня есть доступ только к С++ 17, но это прекрасно решит проблему! Я бы предпочел вторую версию, так как можно предположить знакомство со стандартной библиотекой.
Вы можете взглянуть на библиотеку диапазонов v3 Эрика Ниблера, она должна работать так и является С++ 17 (я думаю), и вы сможете зайти и заменить на stl, как только у вас будет доступ к С++ 20
Одна вещь, которую вы можете сделать, это создать свой собственный общий компаратор, который принимает унарную функцию для выполнения преобразования:
// Generic comparator
template<typename T, typename F>
struct transform_comparator_type
{
transform_comparator_type(F f): f(f) {}
bool operator()(T const& a, T const& b) const { return f(a) < f(b); }
F f;
};
// Helper function to deduce the type of F
template<typename T, typename F>
auto transform_comparator(F f)
{
return transform_comparator_type<T, F>(f);
}
int main()
{
std::vector<int> v{1, 4, 3, 6, 0};
auto e = std::max_element(std::begin(v), std::end(v),
transform_comparator<int>([v](int i){ return 2 * i; }));
// etc...
}
Отредактировано, чтобы добавить:
Фактически тип можно вывести из возвращаемого типа предоставленной функции преобразования, поэтому вам не нужна вспомогательная функция. Вместо этого вы можете сделать это:
template<typename F>
struct transform_comparator
{
using T = decltype(F()({})); // deduce T from return type of F
transform_comparator(F f): f(f) {}
bool operator()(T const& a, T const& b) const { return f(a) < f(b); }
F f;
};
Это выглядит как хорошее решение для С++ 17, а также решает еще более общую проблему. Я также искал что-то подобное в STL или boost, но не нашел, что, по-моему, довольно странно, потому что это распространенная проблема (или нет?).
Я думаю, что большинство людей просто написали бы компаратор как лямбду там, где им это нужно.
Итератор преобразования Boost кажется тем, что вы ищете. Другими словами, следует адаптировать итераторы, которые вы передаете max_element, а не компаратор.