user1324674
user1324674

Reputation: 384

Interruptible Thread class C++11 - getting errors?

I'm following the idea here https://stackoverflow.com/a/13893381/1324674 attempting to create a thread class that will have a static pointer to a boolean flag that will tell a thread to quit (or rather, throw an exception and end). I'm having several issues however while following that same code and I'm wondering how to fix it, I've been working on this for several hours now-

using namespace std;

class InterruptThreadException {};

class InterruptibleThread {
private:
    static thread_local atomic_bool* flagRef;
    atomic_bool flag{false};
    thread thrd;

public:
    friend void checkForInterrupt( );
    template < typename Function, typename... Args >
    InterruptibleThread( Function&& _fxn, Args&&... _args )
        : thrd(
                []( atomic_bool& f, Function&& fxn, Args&&... args ) {
                    flagRef = &f;
                    fxn( forward< Args >( args )... );
                },
                flag,
                forward< Function >( _fxn ),
                forward< Args >( _args )...

                ) {
        thrd.detach( );
    }
    bool stopping( ) const {
        return flag.load( );
    }

    void stop( ) {
        flag.store( true );
    }
};

thread_local atomic_bool* InterruptibleThread::flagRef = nullptr;

void checkForInterrupt( ) {
    if ( !InterruptibleThread::flagRef ) {
        return;
    }
    if ( !InterruptibleThread::flagRef->load( ) ) {
        return;
    }
    throw InterruptThreadException( );
}

void doWork( ) {
    int i = 0;
    while ( true ) {
        cout << "Checking for interrupt: " << i++ << endl;
        checkForInterrupt( );
        this_thread::sleep_for( chrono::seconds( 1 ) );
    }
}

int main( ) {
    InterruptibleThread t( doWork );
    cout << "Press enter to stop\n";
    getchar( );
    t.stop( );

    cout << "Press enter to exit" << endl;
    getchar( );
    return 0;
}

Now I believe the issue is coming from the lambda function inside of the thread call as if I remove that and just have the fxn and args call the program runs fine (obviously without the ability to terminate a thread).

Any idea what I'm doing wrong?

Errors below- visual studio:

Error   C2672   'std::invoke': no matching overloaded function found    
Error   C2893   Failed to specialize function template 'unknown-type std::invoke(_Callable &&,_Types &&...) noexcept(<expr>)'   

gnu: https://gist.github.com/anonymous/2d5ccfa9ab04320902f51d005a96d2ae

Upvotes: 1

Views: 563

Answers (1)

Miles Budnek
Miles Budnek

Reputation: 30569

Your issue is that std::thread stores a copy of its arguments, so the type of Function and the type of the object passed to your lambda aren't the same.

When you call InterruptibleThread(doWork), Function gets deduced to be void (&)(): that is, reference to a function taking no arguments and returning void. When you pass fxn along to std::thread's constructor, it decays to void (*)() (pointer to a function taking no arguments and returning void) and gets stored in some internal storage. When the new thread starts up that internal object gets passed as the second parameter to your lambda, but the type void (*)() doesn't match the expected void (&)() and everything falls apart. Similarly your constructor currently doesn't work if I pass it an lvalue reference to some functor.

A similar problem exists for args. For example, if you pass an lvalue std::string as one of args, it will get passed to the lambda as a std::string&& and things will explode.

The easiest way to fix this is to make the lambda parameter type auto (assuming you're using c++14):

InterruptibleThread( Function&& fxn, Args&&... args )
    : thrd(
            []( atomic_bool& f, auto&& fxn, auto&&... args ) {
                flagRef = &f;
                fxn( std::move(args)... );
            },
            std::ref(flag),
            std::forward<Function>(fxn),
            std::forward<Args>(args)... ) {
}

If you're still on c++11, you'll have to use std::decay to get the correct parameter type:

InterruptibleThread( Function&& fxn, Args&&... args )
    : thrd(
            []( atomic_bool& f,
                typename std::decay<Function>::type&& fxn,
                typename std::decay<Args>::type&&... args ) {
                flagRef = &f;
                fxn( std::move(args)... );
            },
            std::ref(flag),
            std::forward<Function>(fxn),
            std::forward<Args>(args)... ) {
}

Note in both cases I've wrapped flag in a std::reference_wrapper since std::atomic is not copyable and it would be incorrect to make a copy even if it were.

Upvotes: 4

Related Questions