Reputation: 8057
As long as c++ is object-oriented programming language, I'm not sure if std::apply
or std::invoke
are necessary utility, which converts:
Object.Function(Args...)
into:
std::invoke(Object, Function, Args)
that looks like c-style function call. So my question is, when is this kind of scenario a necessary/convenience for daily programming? Or else, is there a case that using apply
/invoke
makes things simpler?
Would you help to give a good example case? Thanks a lot!
Upvotes: 2
Views: 810
Reputation: 1
One of the real use of std::apply
is tuple unpacking, potentially nested. Here's tested example:
#include <iostream>
#include <string>
#include <tuple>
#include <sstream>
// adapted from here: https://stackoverflow.com/a/48458312
template <typename>
constexpr bool is_tuple_v = false;
template <typename ...T>
constexpr bool is_tuple_v<std::tuple<T...>> = true;
template<typename Tval, typename ... T>
void linearize_tuple(std::stringstream &outbuf, const Tval& arg, const T& ... rest) noexcept {
if constexpr (is_tuple_v<Tval>){
outbuf << "{ ";
std::apply([&outbuf](auto const&... packed_values) {
linearize_tuple(outbuf, packed_values ...);
}, arg
);
outbuf << " }";
}
else{
outbuf << arg;
}
if constexpr(sizeof...(rest) > 0){
outbuf << ' ';
linearize_tuple(outbuf, rest ...);
}
}
template<typename ... T>
std::string args_to_string(const T& ... args) noexcept {
std::stringstream outbuf{};
if constexpr(sizeof...(args) > 0){
linearize_tuple(outbuf, args ...);
}
return outbuf.str();
}
int main(){
std::cout << args_to_string(
"test", 1, "2", 3.0, '0', std::tuple
{
"examination", 10, "20", 30.0, '1', std::tuple
{
"we need to go deeper", 100, "200", 300, '2'
}
}
);
}
It will print:
test 1 2 3 0 { examination 10 20 30 1 { we need to go deeper 100 200 300 2 } }
and if you look at implementation of std::apply
, it's probably using std::invoke
like in example from cppreference. Key part:
namespace detail {
template <class F, class Tuple, std::size_t... I>
constexpr decltype(auto) apply_impl(F&& f, Tuple&& t, std::index_sequence<I...>)
{
// This implementation is valid since C++20 (via P1065R2)
// In C++17, a constexpr counterpart of std::invoke is actually needed here
return std::invoke(std::forward<F>(f), std::get<I>(std::forward<Tuple>(t))...);
}
Upvotes: 0
Reputation: 42861
std::invoke
enables all Callable objects to be invoked uniformly, which contains pointers to member functions and pointers to member variables that cannot be invoked with regular function call form like f(args...)
struct S {
void f(int i);
int x;
};
int main() {
auto mem_vptr = &S::x;
auto mem_fptr = &S::f;
S s;
std::invoke(mem_vptr, s); // invoke like s.*mem_vptr;
std::invoke(mem_fptr, s, 0); // invoke like (s.*mem_fptr)(0);
}
Upvotes: 5