void f(int i){}
void f(string s){}
vector<int> int_c = { 1, 2, 3, 4, 5 };
vector<string> string_c = { "Sic" ,"transit" ,"gloria" ,"mundi" };
for_each(begin(int_c), end(int_c), f);//error C2672: "for_each": не найдена соответствующая перегруженная функция
Проблема – в отсутствии параметров у f в последней строке, по которым компилятор мог бы произвести разрешение перегрузки. То, что параметрами for_each передаются итераторы вполне определенного типа – для компилятора не имеет значения.
Можно предложить несколько «лобовых» способов решения проблемы:
1) Использование static_cast<>() для принудительного приведения к указателю на функцию нужного типа.
Приводим к указателю на void f(int i):
std::for_each(begin(int_c), end(int_c), static_cast<void (*)(int)>(&f));
Приводим к указателю на void f(string i):
std::for_each(begin(string_c), end(string_c), static_cast<void (*)(string)>(&f));
2) Определение указателей на функции и их использование.
Используем void f(int i):
void (*fp_int)(int) = &f;
for_each(begin(int_c), end(int_c), fp_int);
Используем void f(string i):
void (*fp_string)(string) = &f;
for_each(begin(string_c), end(string_c), fp_string);
3) Создание явных специализаций для шаблонной функции for_each:
Используем void f(int i):
std::for_each<vector<int>::const_iterator, void(*)(int) >(int_c.begin(), int_c.end(), f);
Используем void f(string i):
std::for_each<vector<string>::const_iterator, void(*)(string) >(string_c.begin(), string_c.end(), f);
или более компактно:
std::for_each<decltype(int_c.begin()), void(*)(int) >(int_c.begin(), int_c.end(), f);
std::for_each<decltype(string_c.begin()), void(*)(string) >(string_c.begin(), string_c.end(), f);
Предложенные выше способы не универсальны и приводят к необходимости разработки на вызывающей стороне дополнительного кода. Лучшей идеей является создание над функцией f тонкой оболочки, принимающей параметры функции f и передающей их в нее без изменений.
Необходимо также учитывать возможности:
- существования неопределенного количества параметров у функции f,
- наличия возвращаемого значения функции,
- передачи параметров по ссылке,
- существования cv-квалификаторы параметров,
- возникновения исключений в функции f.
Поэтапно разберем построение такой оболочки. Лямбда-функция – хорошая основа. При поддержке компилятором стандарта С++11 это можно реализовать так:
for_each(begin(int_c), end(int_c), [](int a) { return f(a); });
for_each(begin(string_c), end(string_c), [](string a) { return f(a); });
или с использованием decltype():
for_each(begin(int_c), end(int_c), [](decltype(*int_c.begin()) a) { return f(a); });
for_each(begin(string_c), end(string_c), [](decltype(*string_c.begin()) a) { return f(a); });
При поддержке компилятором стандарта С++14 имеем для полиморфной лямбды следующее решение:
auto l = [](auto a) { return f(a); };
for_each(begin(int_c), end(int_c), l);
for_each(begin(string_c), end(string_c), l);
Итак, мы передаем параметром в лямбду параметр функции f, и потом вызываем f с этим параметром. Но тут есть проблема: мы не знаем заранее – являются параметры функции f значениями или ссылками? Поэтому необходимо использовать идеальную передачу.
auto l = [](auto&& a) { return f(std::forward<decltype(a)>(a)); };
for_each(begin(int_c), end(int_c), l);
for_each(begin(string_c), end(string_c), l);
По теме идеальной передачи (perfect forwarding) можно прочитать у Мейерса [1]. Более понятный (для меня во всяком случае) вариант изложения того же материала можно найти в статье[2], перевод которой есть на Хабре [3].
Для неопределенного числа параметров функции указываем вариативными параметры лямбды и вызываем std::forward для каждого передаваемого в f параметра.
auto l = [](auto&&... a) { return f(std::forward<decltype(a)>(a)...); };
Добавляем спецификатор времени компиляции noexcept[4] и оператор noexcept[5] для указания компилятору – будет ли функция f выбрасывать исключения[6].
auto l = [](auto&&... a) noexcept(noexcept(f(std::forward<decltype(a)>(a)...))) { return f(std::forward<decltype(a)>(a)...); };
Добавляем вывод типа возвращаемого значения для конструируемой лямбды.
auto l = [](auto&&... a) noexcept(noexcept(f(std::forward<decltype(a)>(a)...))) -> decltype(f(std::forward<decltype(a)>(a)...)) { return f(std::forward<decltype(a)>(a)...); };
Если раздражает трехкратное повторение кода и не гнушаемся использовать макросы – можно уменьшить размер на вызывающей стороне.
#define LIFT(foo) [](auto&&... x) noexcept(noexcept(foo(std::forward<decltype(x)>(x)...))) -> decltype(foo(std::forward<decltype(x)>(x)...)) { return foo(std::forward<decltype(x)>(x)...); }
В итоге имеем оболочку, которая помогает компилятору разрешить перегружаемую функцию.
for_each(begin(int_c), end(int_c), LIFT(f));
for_each(begin(string_c), end(string_c), LIFT(f));
[1] S.Meyers “Effective modern C++” Item 24: Distinguish universal references from rvalue references.
[2] eli.thegreenplace.net/2014/perfect-forwarding-and-universal-references-in-c
[3] habr.com/ru/post/242639
[4] en.cppreference.com/w/cpp/language/noexcept_spec
[5] en.cppreference.com/w/cpp/language/noexcept
[6] habr.com/ru/post/164221
[7] www.fluentcpp.com/2017/08/01/overloaded-functions-stl
[8] www.youtube.com/watch?v=I3T4lePH-yA
Antervis
вот как всегда с макросами — с одной стороны достаточно удобно и практично. С другой — можно использовать лямбду, и будет хоть и маленько подлиннее, но явно и читаемо. Возможно, такая штука была бы удобна в Qt'овском QObject::connect, но там в основном надо разрешать перегрузки сигналов, а для них такой макрос не подойдет. Итого изобретательность поощряю, но использовать не буду
Playa
В Qt есть qOverload
https://doc.qt.io/qt-5/qtglobal.html#qOverload