Alexandre Severino
Alexandre Severino

Reputation: 1613

Transform a tuple of rvalues into a tuple of lvalues

I'm sorry if I'm getting the whole concept wrong, but I'm trying to make a tuple the container of the actual objects, where only with its destruction those objects will go out of scope.

I currently have this:

class MiniThread {
public:
    ~MiniThread() {
        if (m_thread) {
            if (m_thread->joinable())
                m_thread->join();

            delete m_thread;
        }
    }

    void join()
    {
        if (m_thread == nullptr)
            return;

        m_thread->join();
        m_thread = nullptr;
    }

    template<typename F, typename... Args>
    void run(F func, Args... args)
    {
        if (m_thread != nullptr)
            join();

        auto tuple = std::forward_as_tuple(args...);

        m_thread = new std::thread([=]() {
            __try
            {
                std::apply(func, tuple);
            }
            __except (CrashDump::GenerateDump(GetExceptionInformation()))
            {
                // TODO: log.
                exit(1);
            }
        });

        m_started = true;
    }

    bool started() const { return m_started; }

private:
    std::thread *m_thread = nullptr;
    bool m_started = false;
};


std::string getString()
{
    return std::string("sono");
}

int main()
{
    auto test = [&](std::string seila, const std::string& po, std::promise<int>* p)
    {
        std::cout << seila.c_str() << std::endl;
        std::cout << po.c_str() << std::endl;
        p->set_value(10);
    };

    std::promise<int> p;
    std::future<int> f;

    MiniThread thread;
    std::string hello = "hello";
    std::string seilapo = "seilapo";

    f = p.get_future();
    thread.run(test, getString(), "how are you", &p);

    thread.join();
    int ftest = f.get();
    std::cout << ftest << std::endl;
}

By the time the thread is ran, the args are no longer reliable. They have been destructed already. So I was wondering if is there a way to copy them in the call of the thread by value. I have made some attempts of moving the variadic arguments into tuples, but tuples are always rendered with rvalues and fail all the same.

Upvotes: 1

Views: 78

Answers (1)

Barry
Barry

Reputation: 303527

This:

auto tuple = std::forward_as_tuple(args...);

Creates a tuple of references into args... That's what forward_as_tuple's job is. You're then capturing that tuple of references by value:

m_thread = new std::thread([=]{ /* ... */ });

So once your arguments go out of scope, you're only holding onto references to them... and that'll dangle.

But you don't actually... need to have a tuple at all. Just copy the arguments themselves:

m_thread = std::thread([=]() {
    func(args...); // func and args, no tuple here
});

Also don't write new thread - thread is already a handle type, just create one.


The above copies the arguments. If you want to move them, then in C++17, yes you'll need to have a tuple and use std::apply. But not forward_as_tuple... just make_tuple:

m_thread = std::thread([func, args=std::make_tuple(std::move(args)...)]() mutable {
    std::apply(func, std::move(args));
});

In C++20, you won't need the tuple again, and can write a pack-expansion:

m_thread = std::thread([func, ...args=std::move(args)]() mutable {
    func(std::move(args)...);
});

Upvotes: 6

Related Questions