Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

Why can't I std::apply for a member function

I’m trying to develop a wrapper to help people use pthread on calling any member function.

template <typename>
struct signature;

template <typename C, typename R, typename... Args>
struct signature<R (C::*)(Args...)> {
    using return_type        = R;
    using pure_argument_type = std::tuple<C*, std::tuple<Args...>>; // obj, {args...}
    using argument_type =
        std::tuple<R (C::*)(Args...), pure_argument_type>; // obj.mem_fn, {obj, args...}
};
    
struct Job {
public:
    Job()  = default;
    ~Job() = default;

    void join() { pthread_join(btd, nullptr); }

    template <typename C, typename F, typename... Args>
    int run(F C::*f, C* c, Args&&... args) {
        typename signature<decltype(f)>::pure_argument_type pureParams =
            std::make_tuple(c, std::forward_as_tuple(args...));
        typename signature<decltype(f)>::argument_type params = std::make_tuple(f, pureParams);

        return pthread_create(
            &btd, nullptr,
            [](void* p) {
                auto param = static_cast<typename signature<decltype(f)>::argument_type*>(p);
                std::apply(std::get<0>(*param), std::get<1>(*param)); ////// ERROR!
                return (void*)nullptr;
            },
            &params);
    }

private:
    pthread_t btd;
};

However, when I try to do as below,

class Test {
public:
    void func(int& a, double& d) { a = 2; d = 2.0; }

    void dojob() {
        int a = 0;
        double d = 0.0;
        Job job;
        job.run(&Test::func, this, std::ref(a), std::ref(d));
        job.join();
        std::cout << "a:" << a << " d:" << d << std::endl;
    }
};

Test t;
t.dojob();

I get the error:

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

In file included from /opt/compiler-explorer/gcc-11.2.0/include/c++/11.2.0/bits/stl_map.h:63,
                 from /opt/compiler-explorer/gcc-11.2.0/include/c++/11.2.0/map:61,
                 from <source>:2:
/opt/compiler-explorer/gcc-11.2.0/include/c++/11.2.0/tuple: In instantiation of 'constexpr decltype(auto) std::__apply_impl(_Fn&&, _Tuple&&, std::index_sequence<_Idx ...>) [with _Fn = void (Test::*&)(int&, double&); _Tuple = std::tuple<Test*, std::tuple<int&, double&> >&; long unsigned int ..._Idx = {0, 1}; std::index_sequence<_Idx ...> = std::integer_sequence<long unsigned int, 0, 1>]':
/opt/compiler-explorer/gcc-11.2.0/include/c++/11.2.0/tuple:1854:31:   required from 'constexpr decltype(auto) std::apply(_Fn&&, _Tuple&&) [with _Fn = void (Test::*&)(int&, double&); _Tuple = std::tuple<Test*, std::tuple<int&, double&> >&]'
<source>:98:27:   required from 'int Job::run(F C::*, C*, Args&& ...) [with C = Test; F = void(int&, double&); Args = {std::reference_wrapper<int>, std::reference_wrapper<double>}]'
<source>:118:16:   required from here
/opt/compiler-explorer/gcc-11.2.0/include/c++/11.2.0/tuple:1843:27: error: no matching function for call to '__invoke(void (Test::*&)(int&, double&), Test*&, std::__tuple_element_t<1, std::tuple<Test*, std::tuple<int&, double&> > >&)'
 1843 |       return std::__invoke(std::forward<_Fn>(__f),
      |              ~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~
 1844 |                            std::get<_Idx>(std::forward<_Tuple>(__t))...);
      |

You can get my code here: https://godbolt.org/z/sMjcxvP33

>Solution :

The return type of std::get<1>(*param) is std::tuple<C*, std::tuple<Args...>>.

Since the type of the second element of this tuple is also a tuple, you need to use std::apply to expand it again, something like this

std::apply(
  [f = std::get<0>(*param)](auto* obj, auto&& args) {
    std::apply([&](auto&&... args) { 
      (obj->*f)(std::forward<decltype(args)>(args)...); 
    }, std::forward<decltype(args)>(args));
  },
  std::get<1>(*param));

Demo

Another alternative is to flatten the nested tuple to std::tuple<C*, Args...> and then pass it to std::apply along with f.

std::apply(
  std::get<0>(*param),
  std::tuple_cat(
    std::make_tuple(std::get<0>(std::get<1>(*param))),
                    std::get<1>(std::get<1>(*param))));

Demo

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading