#опытным
Продолжаем перечислять все сущности, которые можно "вызывать" в С++.
И начинаем с продолжения перечисления всего, что относится к функторам:
👉🏿👉🏿 Лямбды
Там, где говорят про функторы, всегда водятся лямбды. Лямбды - это такие карманные функциональные объекты, которые определяются на ходу. У них также определен соответствующий оператор вызова operator(). Вот как может выглядеть код из предыдущего пункта с помощью лямбды:
int start = 5;
int step = 2;
auto gen = [current = start, step]() mutable {
return std::exchange(current, current + step);
};
std::vector<int> numbers(5);
std::generate(numbers.begin(), numbers.end(), gen);
Накидайте огней, если хотите узнать, как 3 строчки из лямбды в предыдущем посте превратились в одну с помощью std::exchange)
👉🏿👉🏿 Объект класса с определенным оператором приведения к указателю на функцию
Солидная часть подписчиков даже не слышала о такой сущности. Но работает это тривиально: при "вызове" такого объекта он кастится к указателю на функцию и уже с его помощью реально делается вызов:
class FunctionObjectCast {
public:
using fun_ptr = int ()(int);
// Оператор приведения к указателю на функцию
operator fun_ptr() {
return &staticMethod;
}
// Статический метод, который будет вызываться через указатель
static int staticMethod(int x) {
return x * 2;
}
};
FunctionObjectCast obj;
obj(42); // cast hereНо даже, если вы ни разу не слышали про функторы такого вида, то с высокой долей вероятности все равно неявно пользовались им. Это конечно же лямбды без захвата.
👉🏿👉🏿 Лямбды без захвата. Именно с помощью оператора приведения к указателю на функцию лямбды без захвата можно маскировать под указатели на функции. Это позволяет использовать их в качестве коллбэков к сишным функциям или колдовать такую магию.
std::vector<int> nums = {5, 3, 9, 1};
std::qsort(nums.data(), nums.size(), sizeof(int),
[](const void* a, const void* b) -> int {
return ((int)a - (int)b);});std::qsort принимает последним параметром именно указатель на функцию, поэтому конкретно в этом кейсе и срабатывает приведение к этому указателю.
В случае привычного вызова самой лямбды используется operator().
👉🏿 Нестатический метод
struct Handler {
void foo(int a) {
std::cout << "Non-static method: " << a << std::endl;
}
};
Handler{}.foo(42);👉🏿 Указатель на нестатический метод. Если вы хотите передать метод класса в качестве коллбэка, то вам необходимо это делать через указатель на метод. Метод класса - это такая же функция, только первым параметром она неявно принимает this. Если делать явный вызов метода через указатель на него, то эта неявная передача становится явной:
struct Handler {
void foo(int a) {
std::cout << "Non-static method: " << a << std::endl;
}
};
auto non_static_method_ptr = &Handler::foo;
std::invoke(non_static_method_ptr, Handler{}, 42);Чтобы вызвать метод класса по его указателю через std::invoke нужно вторым параметром передать объект класса, на котором мы хотим вызвать метод. Далее идут аргументы в порядке, определенном в методе.
Замечу, что использования оператора взятия адреса(&) перед именем метода здесь обязательно. Имена нестатических методов не имеют желания неявно приводится к указателям, как имена функций. Вот так нельзя:
std::invoke(Handler::foo, Handler{}, 42);Это синтаксис для использования статических методов и компилятор будет ругаться.
👉🏿 Указатель на поле класса. Недавно обсуждали, что с помощью std::invoke можно "вызывать" поля класса. Неочевидно, но бывает полезно.
struct Payment {
double amount;
std::string category;
}
Payment p{100500, "confetki"};
std::cout << std::invoke(&Payment:amount, p) << std::endl;
// OUTPUT:
// 100500Фух. Вроде все. Пишите, если что забыл.
Have all the tools. Stay cool.
#cppcore