How to pass template parameter of a function to a lambda in the function

144 Views Asked by At
  template <typename C, typename F, typename... Args>
  void MyClass::run(F C::*f, C* c, Args&&... args) {
    td_ = std::make_unique<std::thread>(
        [](F C::*f, C* c, Args... args){    // Is this correct?
            c.f(args...);    // should I code c.f(std::forward<Args>(args)...); here?
        });
  }

I know what std::forward is used for. But in the case above, the types of parameters are passed to a lambda, which is in the template function. I'm kind of confused now...

2

There are 2 best solutions below

0
Pepijn Kramer On BEST ANSWER
// The perfect forwarding is a bit of a thing, and you need std::apply
// Also when writing a library like this you should give client code a chance 
// to syncrhonize with the threads you spawn
// AND you have to think about exception handling on the threads you spawn
// never trust your clients not to add a function that will not throw.
// In this example I rely on std::async/std::future to do that for now.


#include <future>
#include <type_traits>
#include <iostream>

//-----------------------------------------------------------------------------

template<typename fn_t>
auto run(fn_t fn) 
{
    return std::async(std::launch::async, fn);
}

template <typename class_t, typename fn_t, typename... args_t>
auto run(class_t& c, fn_t class_t::*fn, args_t&&... args)
{
    // make a local lambda first so we can reuse impl of run
    // perfect forwarding of args
    // mutable is necessary to be able to call non-const methods on class_t
    auto lambda = [fn=fn, args = std::make_tuple(c, std::forward<args_t>(args) ...)]() mutable
    {
        return std::apply(fn, args);
    };

    return run(lambda);
}

//-----------------------------------------------------------------------------

struct test_t
{
    void fn(int value) 
    {
        std::cout << "test_t::fn(" << value << ")\n";
    };

    void fn_noexcept(int value) noexcept 
    {
        std::cout << "test_t::fn_noexcept(" << value << ")\n";
    };
};

//-----------------------------------------------------------------------------

int main()
{
    test_t test;

    auto future1 = run([&] { test.fn(1); });
    future1.get(); // give output a chance to appear

    auto future2 = run(test, &test_t::fn,2);
    future2.get(); // give output a chance to appear

    return 0;
}
0
fabian On

There are a few problems here:

  • std::thread itself is moveable, so wrapping it in a std::unique_ptr may be entirely unnecessary
  • std::thread copies any function arguments to the background thread.
  • You need to pass the arguments to the functor to be called on the background thread to the constructor of std::thread; any parameters of the function to be called in the thread that are parameters of a refercence type that should reference the objects on the thread creating the thread need to be wrapped in std::reference_wrapper to be passed to the background thread without a copy of the object being created.
  • There are a few other syntax errors in the function.

In this scenario you likely shouldn't be using perfect forwarding, but simply provide functionality for choosing the appropriate conversion for the parameter to be passed to the thread. Here's a modification of your code that should accomplish what you're trying to do:

// helper type for passing everything other than lvalue references by value

template<class T>
struct ThreadPassType
{
    using value_type = T&&;
};

template<class T>
struct ThreadPassType<T&>
{
    using value_type = std::reference_wrapper<T>;
};

struct MyClass
{

    std::thread td_;

    template<typename C, typename F, typename... Args>
    void run(F (C::*f)(Args...), std::type_identity_t<C>* c, std::type_identity_t<Args>... args)
    //                           ^^^^^^^^^^^^^^^^^^^^        ^^^^^^^^^^^^^^^^^^^^^^^^^^
    // note: we're excluding anything from deducing the template parameters except for the member function pointer
    {
        td_ = std::thread(
                f, // std::thread deals with member function pointers on its own
                c,
                static_cast<ThreadPassType<Args>::value_type>(args)...);
    }
};

struct TestType
{
    void print(std::string const& s)
    {
        std::cout << static_cast<void const*>(&s) << ": " << s << '\n';
    }

    void test(std::string s0, std::string& s1, std::string const& s2, std::string&& s3)
    {
        std::cout << "background thread:\n";
        print(s0);
        print(s1);
        print(s2);
        print(s3);
    }
};

int main()
{
    TestType t;

    std::string s0 = "a";
    std::string s1 = "b";
    std::string s2 = "c";
    std::string s3 = "d";

    std::cout << "main thread(before):\n";
    t.print(s0);
    t.print(s1);
    t.print(s2);
    t.print(s3);

    MyClass mc;
    mc.run(&TestType::test, &t, s0, s1, s2, std::move(s3));
    // deduced template parameters: <TestType, void, std::string, std::string&, std::string const&, std::string&&>

    mc.td_.join();

    std::cout << "main thread(after):\n";
    t.print(s0);
    t.print(s1);
    t.print(s2);
    t.print(s3);
}