1
  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...

5
  • Why not just accept another lambda template<typename fn_t> run(fn_t fn) { td_ = std::make_unique<std_thread>(f); } and let the lambda expression do all that heavy lifting. You might then also use SFINAE (or concepts) to only accept functions without return statements and noexcept specification (because you can't return anything and you cannot handle exceptions anymore) Commented Jul 1, 2023 at 3:44
  • @PepijnKramer Well, I'm developing a low level library, and the users of the library said they want to use this function, just like std::thread... Commented Jul 1, 2023 at 3:48
  • Don’t you want to just capture run’s parameters (including the args pack)? Commented Jul 1, 2023 at 3:57
  • @DavisHerring Yes, I want to use run's parameters as the parameters of the lambda. So I could call run like this: run(MyClass:Test, &myClassObj, param1, param2); Commented Jul 1, 2023 at 4:09
  • @Yves That's a good reason to support it. Commented Jul 1, 2023 at 4:11

2 Answers 2

2
// 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;
}
Sign up to request clarification or add additional context in comments.

Comments

2

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.