1

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

RAM
  • 2,257
  • 2
  • 19
  • 41
user1324674
  • 384
  • 1
  • 3
  • 12

1 Answers1

4

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.

Miles Budnek
  • 28,216
  • 2
  • 35
  • 52
  • Thank you so much you have no idea how long I was working on this, not knowing how to come to a solution. – user1324674 Aug 09 '17 at 22:26
  • how would I go about getting this to work for threads that launch member functions? Generally I would launch a thread like thread(&Class::function, this, args....) but I'm getting an error with the interruptible thread trying this same thing (even though it worked otherwise): https://gist.github.com/anonymous/5f023198620e35667c0a158d13772334 – user1324674 Aug 10 '17 at 14:45
  • @user1324674 You should ask that as a new question. Pointer-to-member objects use a different syntax. – Miles Budnek Aug 10 '17 at 16:07
  • if you dont mind, I have some follow up questions as I'm disassembling all the code changes and ideas you've introduced so I can better understand what is going on- I wanna start with this part here, you said when I pass fxn to thread's constructor it is being decayed to (*)(), but why is this? if _fxn is a reference that is being forwarded to the lambda, should it not be a reference as well? Or am I wrong about what forward is doing? – user1324674 Aug 11 '17 at 22:09
  • Read [the documentation](http://en.cppreference.com/w/cpp/thread/thread/thread) for `std::thread`'s constructor. It decays because the standard says it does. All references passed to `std::thread`'s constructor do so. – Miles Budnek Aug 12 '17 at 00:58