The std::invoke
function in the C++ standard library is usually used to call a functor with parameters.
std::function<void(int)> func = ...; // same as func(42) std::invoke(func, 42);
What std::invoke
brings to the table is that you can use it for other things beyond just functors.
struct S { void do_something(int); int v; }; S s; // same as s.do_something(42) std::invoke(&S::do_something, s, 42); std::invoke(&S::do_something, std::ref(s), 42); S* p = &s; // same as p->do_something(42) std::invoke(&S::do_something, p, 42);
But wait, what about this?
struct S { std::function<void()> do_something; int v; }; S s; s.do_something = []() { std::cout << "hello"; }; // does not print anything std::invoke(&S::do_something, s);
What’s going on here?
One thing that often goes overlooked is that you can also use std::invoke
with pointers to non-static data members.
S s; // same as s.v = 42 std::invoke(&S::v, s) = 42; // same as "auto x = s.v;" auto x = std::invoke(&S::v, s);
Invoking a pointer to a non-static data member is the same as dereferencing the pointer, when applied to the second argument.
The statement
std::invoke(&S::do_something, s);
is therefore equivalent to
s.do_something;
which, despite its name, does nothing: It accesses the member and throws it away.
If you want to access the memory and then invoke it, you’ll have to follow up the std::invoke
with the function call.
std::invoke(&S::do_something, s)();
Or, if you really like to show off,
std::invoke(std::invoke(&S::do_something, s));
Taken to an extreme, you get invoke-oriented programming!
// Old and busted this->dict.find(3)->second = "meow"; // New hotness std::invoke( static_cast<std::map<int, std::string>::iterator (std::map<int, std::string>::*)(int const&)>( &std::map<int, std::string>::find), std::invoke(&MyClass::dict, this), 3)->second = "meow"; // Beyond hot std::invoke( static_cast<std::string& (std::string::*)(char const*)> (&std::string::operator=), std::invoke(&std::pair<int const, std::string>::second, std::invoke( static_cast<std::pair<int const, std::string>& ( std::map<int, std::string>::iterator::*)() const noexcept> (&std::map<int, std::string>::iterator::operator*), std::invoke( static_cast<std::map<int, std::string>::iterator (std::map<int, std::string>::*)(int const&)> (&std::map<int, std::string>::find), std::invoke(&MyClass::dict, this), 3))), "meow");
The above code is technically non-portable thanks to [member.functions], which says
For a non-virtual member function described in the C++ standard library, an implementation may declare a different set of member function signatures, provided that any call to the member function that would select an overload from the set of declarations described in this document behaves as if that overload were selected.
This means basically that you cannot form pointers to non-virtual member functions, because the implementation’s signature for the member function is permitted to differ from the formal definition (say, by the addition of default template arguments or parameters), as long as the behavior is the same. In practice, these extra default arguments or parameters are used for things like SFINAE.
To make the code portable, we’ll have to wrap the member function pointers into program-provided versions.
namespace mfptr { template<typename Object, typename...Args> decltype(auto) find(Object&& object, Args&&...args) { return std::forward<Object>(object).find(std::forward<Args>(args)...); } template<typename Object> decltype(auto) dereference(Object&& object) { return *std::forward<Object>(object); } template<typename Object, typename Arg> decltype(auto) assign(Object&& object, Arg&& arg) { return std::forward<Object>(object) = arg; } } std::invoke( &mfptr::assign<std::string&, char const*>, std::invoke(&std::pair<int const, std::string>::second, std::invoke( &mfptr::dereference<std::map<int, std::string>::iterator>, std::invoke( &mfptr::find<std::map<int, std::string>&, int>, std::invoke(&MyClass::dict, this), 3))), "meow");
C++23’s deducing this should greatly simplify these cases for me (obviating std::invoke for my uses anyway) as I can now consistently call either a static function taking a class/struct or a semistatic method on a class/struct via the same function pointer. Been playing with it already in VS2022 Preview 2.
I don't quite see the point of . The only 2 cases where it extends the usual function call syntax are non-static member functions and variables; it may be useful for to support member functions (using a lambda to wrap the member function would require variadic templates and perfect forwarding, which is verbose), but supporting member variables is just confusing; I just can't conceive anyone who would find this feature useful.
And even then,...
Raymond's teasing a bit because of April Fool's, but invoke is meant to lessen the need for callers to use things like mem_fn. For instance, the ranges algorithms use invoke extensively, so using simple member based selections for projections need less syntax noise than they do with the classic algorithms.
https://gcc.godbolt.org/z/WeY594Ma5
Also note that even the "classic" callable wrappers like mem_fn, bind, and function support pointers to member data as a callable.
https://gcc.godbolt.org/z/ndh4Eo9Mv
Which makes sense since member...