2

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!

康桓瑋
  • 33,481
  • 5
  • 40
  • 90
Troskyvs
  • 7,537
  • 7
  • 47
  • 115
  • Object.Function is only possible if the called function is known at compile time while std::invoke can call every callable entity even given by a variable and changed in runtime. – Klaus Aug 01 '22 at 08:48
  • 1
    C++ supports OO but it's not only OO. Did you check [`std::invoke`](https://en.cppreference.com/w/cpp/utility/functional/invoke) reference for examples already? – paolo Aug 01 '22 at 08:49
  • BTW: OO did not mean that free functions should go away at all! There are many many reasons to go with free functions instead of member calls. The list of arguments is to long for a comment and also to long for an answer! – Klaus Aug 01 '22 at 08:50
  • 3
    C++ is not a OO-language. Its a multi-paradigm language. – 463035818_is_not_an_ai Aug 01 '22 at 09:01

2 Answers2

5

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);
}
康桓瑋
  • 33,481
  • 5
  • 40
  • 90
  • 3
    An important note is that you will often do this in a template, where you take the Callable as a (deduced) parameter – Caleth Aug 01 '22 at 09:05
0

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))...);
}