библиотека — вызов Callablelibrary — calling a Callable
Единообразный вызов чего угодно вызываемого — функции, лямбды, указателя на метод, указателя на поле — и вызов функции с аргументами, уже собранными в tuple.A uniform way to call anything callable — a function, a lambda, a member-function pointer, a data-member pointer — and to call a function with arguments already packed into a tuple.
std::invoke(f, args...) вызывает f с аргументами args... независимо от того, что такое f: обычная функция или функциональный объект — вызов эквивалентен f(args...); указатель на функцию-член класса (&Class::method) — первым аргументом ожидается объект или указатель на объект этого класса, дальше обычные аргументы метода; указатель на данные-член (&Class::field) — с единственным аргументом-объектом возвращает значение этого поля. std::apply(f, tuple) вызывает f с аргументами, распакованными из tuple по порядку: apply(f, std::make_tuple(1, 2)) эквивалентно f(1, 2).std::invoke(f, args...) calls f with arguments args... regardless of what f is: an ordinary function or a function object — the call is equivalent to f(args...); a pointer to a class member function (&Class::method) — the first argument is expected to be an object or a pointer to an object of that class, followed by the method's ordinary arguments; a pointer to a data member (&Class::field) — with a single object argument it returns that field's value. std::apply(f, tuple) calls f with arguments unpacked from tuple in order: apply(f, std::make_tuple(1, 2)) is equivalent to f(1, 2).
struct Widget { void draw() const; };
void callDraw(void(Widget::*m)() const, const Widget& w) {
(w.*m)(); // синтаксис .* — несовместим с обычным m(w)// .* syntax — incompatible with an ordinary m(w)
}
// generic-код, принимающий "что угодно вызываемое",// generic code accepting "anything callable"
// не мог единообразно обработать этот случай без ручных проверок// couldn't handle this case uniformly without manual checks
struct Widget { void draw() const; };
template <class F, class... Args>
void callAnything(F&& f, Args&&... args) {
std::invoke(std::forward<F>(f), std::forward<Args>(args)...);
}
callAnything(&Widget::draw, w); // работает: invoke сам понимает, что это member pointer// works: invoke figures out it's a member pointer itself
callAnything([]{ std::cout << "hi"; }); // тоже работает — обычная лямбда// also works — an ordinary lambda
Шаблонная функция, принимающая параметр F как "что угодно вызываемое", до invoke не могла просто написать f(args...) — если вызывающий код передавал указатель на функцию-член, требовался отдельный синтаксис (obj.*f)(args) или (obj_ptr->*f)(args). Без if constexpr/type traits на каждый случай применения такой generic-код не мог обработать оба варианта одной строкой.A template function taking a parameter F as "anything callable" couldn't, before invoke, simply write f(args...) — if the caller passed a pointer to a member function, a separate (obj.*f)(args) or (obj_ptr->*f)(args) syntax was required. Without an if constexpr/type-trait check for every use case, such generic code couldn't handle both variants in one line.
std::function, std::thread и std::bind под капотом всегда умели принимать и обычные функции, и member pointers, и вызывали их единообразно — но до C++17 эта "INVOKE-семантика" была описана в стандарте абстрактно, как требование к их реализации, без публичной функции, которую мог бы использовать и прикладной generic-код. std::invoke сделал этот механизм явным и переиспользуемым.std::function, std::thread, and std::bind had always been able under the hood to accept both ordinary functions and member pointers and call them uniformly — but before C++17 this "INVOKE semantics" was described in the standard abstractly, as a requirement on their implementation, without a public function that application-level generic code could also use. std::invoke made this mechanism explicit and reusable.
Аргументы для вызова часто оказываются уже собранными как tuple — например, из structured bindings, из variadic-template forwarding, или как результат другой функции. До apply развернуть tuple в отдельные аргументы вызова можно было только вручную через std::index_sequence и рекурсивный шаблон — apply делает это одной строкой.The arguments for a call often turn out to already be packed as a tuple — say, from structured bindings, from variadic-template forwarding, or as the result of another function. Before apply, unpacking a tuple into separate call arguments could only be done by hand via std::index_sequence and a recursive template — apply does it in one line.
#include <functional>
#include <tuple>
struct Point {
int x, y;
int sum() const { return x + y; }
};
Point p{3, 4};
// invoke с обычной функцией — как обычный вызов:// invoke with an ordinary function — like an ordinary call:
std::invoke(printf, "%d\n", 42);
// invoke с указателем на метод — объект вторым аргументом:// invoke with a member-function pointer — the object is the second argument:
int s = std::invoke(&Point::sum, p); // эквивалент p.sum()// equivalent to p.sum()
// invoke с указателем на поле — тоже объект вторым аргументом:// invoke with a data-member pointer — also the object as the second argument:
int xv = std::invoke(&Point::x, p); // эквивалент p.x// equivalent to p.x
// apply — вызов с аргументами, распакованными из tuple:// apply — a call with arguments unpacked from a tuple:
auto args = std::make_tuple(1, 2, 3);
int total = std::apply([](int a, int b, int c) { return a + b + c; }, args);
std::invoke редко пишут напрямую в прикладном коде — это низкоуровневая утилита, на которой строятся другие generic-компоненты. Если в коде встречается прямой вызов std::invoke, скорее всего это реализация чего-то вроде обёртки над Callable, кастомного std::function-подобного типа или generic-адаптера, а не типичный бизнес-код.std::invoke is rarely written directly in application code — it's a low-level utility other generic components are built on. If direct use of std::invoke shows up in code, it's most likely the implementation of something like a wrapper over a Callable, a custom std::function-like type, or a generic adapter, not typical business code.
std::invoke(&Point::sum, p), а не std::invoke(p, &Point::sum) — объект передаётся вторым аргументом, что противоречит интуиции обычного вызова метода p.sum(), где объект стоит первым. Перепутать порядок — частая ошибка при первом знакомстве с этой фичей.std::invoke(&Point::sum, p), not std::invoke(p, &Point::sum) — the object is passed as the second argument, which contradicts the intuition of an ordinary method call p.sum(), where the object comes first. Mixing up the order is a common mistake when first encountering this feature.
Встретив std::invoke в чужом коде, стоит воспринимать это как сигнал "это generic library code", а не типичная бизнес-логика — обычный вызов функции или лямбды пишется как f(args...) напрямую, invoke нужен именно тогда, когда тип f неизвестен на этапе написания шаблона.Encountering std::invoke in someone else's code should be read as a signal that "this is generic library code," not typical business logic — an ordinary function or lambda call is written as f(args...) directly; invoke is needed precisely when the type of f is unknown at the point the template is written.
Второй аргумент apply должен поддерживать tuple-протокол (std::tuple_size, get<N>) — обычный std::vector или динамический массив сюда не подходит, потому что число и типы элементов должны быть известны на этапе компиляции. Для runtime-известного числа аргументов apply не применим в принципе.apply's second argument must support the tuple protocol (std::tuple_size, get<N>) — an ordinary std::vector or dynamic array doesn't fit here, because the number and types of elements must be known at compile time. For a runtime-known number of arguments, apply doesn't apply at all.