Skip to content
C++
Library
since C++17
Intermediate

std::invoke

Uniformly call any callable (function, member pointer, functor, lambda) with std::invoke (C++17), std::invoke_r (C++23), and std::is_invocable traits.

std::invokesince C++17

A function template in <functional> that calls any callable object, including free functions, function objects, lambdas, pointers to member functions and pointers to data members, using one uniform call syntax.

Overview

C++ has several kinds of "callable" that use different call syntax: f(args) for functions and functors, (obj.*pmf)(args) or (ptr->*pmf)(args) for member function pointers, and obj.*pmd for data member pointers. Generic code that must accept all of them used to need special cases.

The standard library has long defined an abstract operation called INVOKE that unifies these cases. It was used by std::function, std::bind and std::thread since C++11, but was not directly exposed. Since C++17, std::invoke exposes it. Since C++23, std::invoke_r adds a variant with an explicit return type.

For std::invoke(f, t1, t2, ..., tN), the rules are:

Callable fFirst arg t1Effect
Member function pointer of class TT, derived from T, or reference_wrapper<T>(t1.*f)(t2...)
Member function pointerPointer-like (raw or smart pointer)((*t1).*f)(t2...)
Data member pointer (N == 1)objectt1.*f
Data member pointer (N == 1)pointer-like(*t1).*f
Anything elsen/af(t1, ..., tN)

The return type is std::invoke_result_t<F, Args...>. Value category is preserved: a call returning a reference returns a reference, and arguments are perfectly forwarded.

Since C++17, std::invoke is constexpr conditionally. Since C++20 it is also noexcept when the underlying call is (it was already conditionally noexcept from C++17; check your implementation for earlier modes).

Syntax

cpp
#include <functional>
#include <type_traits>

// C++17
template<class F, class... Args>
std::invoke_result_t<F, Args...>
invoke(F&& f, Args&&... args) noexcept(std::is_nothrow_invocable_v<F, Args...>);

// C++23: R is the explicit result type; the result is implicitly converted to R
template<class R, class F, class... Args>
constexpr R invoke_r(F&& f, Args&&... args) noexcept(std::is_nothrow_invocable_r_v<R, F, Args...>);

// Related traits
// C++11: std::result_of (deprecated in C++17, removed in C++20)
// C++17: std::invoke_result, std::is_invocable, std::is_nothrow_invocable
// C++17: std::is_invocable_r, std::is_nothrow_invocable_r

std::invoke_r<void> discards the result. For non-void R, invoke_r avoids a dangling temporary: if R is a reference and the result would bind to a temporary, the program is ill-formed (C++23).

Examples

Uniform dispatch in a generic wrapper

cpp
#include <functional>
#include <iostream>
#include <string>
#include <utility>

struct Widget {
    int id = 7;
    std::string name() const { return "widget-" + std::to_string(id); }
    int scaled(int k) const { return id * k; }
};

int twice(int x) { return 2 * x; }

// C++17: works with every callable kind
template <class F, class... Args>
decltype(auto) timed_call(const char* label, F&& f, Args&&... args) {
    std::cout << "[call] " << label << '\n';
    return std::invoke(std::forward<F>(f), std::forward<Args>(args)...);  // C++17
}

int main() {
    Widget w;
    Widget* pw = &w;

    std::cout << timed_call("free fn",   twice, 21) << '\n';
    std::cout << timed_call("lambda",    [](int a, int b) { return a + b; }, 1, 2) << '\n';
    std::cout << timed_call("mem fn",    &Widget::scaled, w, 3) << '\n';
    std::cout << timed_call("via ptr",   &Widget::name, pw) << '\n';
    std::cout << timed_call("data mem",  &Widget::id, w) << '\n';
}

Without std::invoke, f(args...) would fail to compile for the member pointer cases.

Projections and member pointers in algorithms

Since C++20, the constrained algorithms in std::ranges call projections through std::invoke, so member pointers work directly:

cpp
#include <algorithm>
#include <vector>
#include <string>

struct Employee { std::string name; int salary; };

int main() {
    std::vector<Employee> staff{{"Ada", 120}, {"Bob", 90}, {"Cy", 100}};

    // C++20: &Employee::salary is invoked as a projection
    std::ranges::sort(staff, std::less{}, &Employee::salary);
}

The classic std::sort (C++98) and std::for_each do not use INVOKE for their comparator or function arguments, so passing a member pointer to them does not compile. Wrap it with std::mem_fn (C++11), std::bind, or a lambda.

Calling a tuple of arguments

cpp
#include <tuple>
#include <functional>
#include <iostream>

int add3(int a, int b, int c) { return a + b + c; }

int main() {
    auto args = std::make_tuple(1, 2, 3);
    std::cout << std::apply(add3, args) << '\n';  // C++17; std::apply uses INVOKE internally
}

Detecting callability (C++17 traits)

cpp
#include <type_traits>
#include <string>

struct S { int f(double) const; };

static_assert(std::is_invocable_v<decltype(&S::f), const S&, double>);      // C++17
static_assert(!std::is_invocable_v<decltype(&S::f), const S&>);             // arity mismatch
static_assert(std::is_invocable_r_v<long, decltype(&S::f), S, double>);     // convertible to long
static_assert(std::is_same_v<
    std::invoke_result_t<decltype(&S::f), S, double>, int>);                // C++17

template <class F, class... A>
auto maybe_call(F&& f, A&&... a) {
    if constexpr (std::is_invocable_v<F, A...>) {                           // C++17
        return std::invoke(std::forward<F>(f), std::forward<A>(a)...);
    } else {
        return 0;
    }
}

invoke_r (C++23)

cpp
#include <functional>
#include <string>

// C++23
void run_callback() {
    auto cb = [] { return 42; };

    // Discard the result explicitly
    std::invoke_r<void>(cb);

    // Convert result to long without a separate cast
    long v = std::invoke_r<long>(cb);

    // Convert to std::string from a const char* result
    auto s = std::invoke_r<std::string>([] { return "hello"; });
    (void)v; (void)s;
}

invoke_r is what std::move_only_function (C++23) and std::function_ref (C++26) rely on conceptually to apply return-type conversions with the correct semantics.

Best Practices

  • Use std::invoke in every generic function that accepts a callable. If your template says f(args...), it rejects member pointers; std::invoke(f, args...) accepts them.
  • Forward the callable and arguments. Use std::invoke(std::forward<F>(f), std::forward<Args>(args)...) and return decltype(auto) so references and value categories are preserved.
  • Constrain with std::is_invocable_v or concepts. Since C++20, prefer std::invocable<F, Args...> and std::regular_invocable from <concepts> over hand-written SFINAE.
  • Propagate noexcept. Use noexcept(std::is_nothrow_invocable_v<F, Args...>) on wrapper functions so callers see accurate exception specs.
  • Replace std::result_of. It was deprecated in C++17 and removed in C++20. Use std::invoke_result_t.
  • Prefer a lambda over std::bind for ad-hoc adaptation, and use std::invoke when you only need dispatch, not partial application.

Common Pitfalls

  • Pre-C++17 availability. std::invoke does not exist before C++17. If you must support C++14, write your own helper using std::result_of and overloads on std::is_member_pointer, or use std::mem_fn in specific cases.
  • Overloaded functions and templates. std::invoke(twice_overloaded, 1) fails because the name does not identify one function. Wrap the call in a lambda or cast to a specific function pointer type. Static member functions and std::mem_fn have the same limitation.
  • Data member pointers take exactly one argument. std::invoke(&Widget::id, w, 1) is ill-formed.
  • Lifetime of the object. std::invoke(&Widget::name, Widget{}) is fine when the result is a value, but if the member function returns a reference into the temporary, using it after the full expression dangles.
  • Reference wrappers. std::reference_wrapper<T> as the first argument is unwrapped for member pointers. For plain callables, std::reference_wrapper<F> is itself callable and forwards to F, but it will not unwrap other arguments.
  • Const and ref qualifiers. std::invoke(&S::f, const_s) fails if f is not const. Likewise, a &&-qualified member function requires an rvalue object.
  • Implicit conversions with invoke_r. invoke_r<R> allows any implicit conversion from the result to R, which can hide narrowing or unwanted construction. Pick R deliberately, and use void only when discarding is the intent.
  • Compile-time cost. Heavy use of std::invoke in deeply nested templates adds instantiations. If this shows up in build profiles, avoid it in hot header paths where the callable type is known to be a plain functor.
  • constexpr support. std::invoke is constexpr only since C++20. In C++17 mode it cannot be used in constant expressions on conforming implementations, even for constexpr callables.

See Also

  • std::bind: partial application built on INVOKE (C++11).
  • std::any: type-erased value storage (C++17), often paired with callable registries.
  • std::function (C++11), std::move_only_function (C++23), std::function_ref (C++26): type-erased callable wrappers with INVOKE semantics.
  • std::mem_fn (C++11), std::apply (C++17), std::invoke_result (C++17), std::is_invocable (C++17).