1

This is a followup to a question I got answered yesterday.

I'm trying to make an interrupt thread for member functions but I have no clue what these errors are:

[vagrant@localhost projects]$ g++ -o test test.cpp -lpthread
test.cpp: In instantiation of ‘InterruptibleThread::InterruptibleThread(Function&&, Args&& ...)::<lambda(std::atomic_bool&, std::atomic_bool&, auto:1&&, Args&& ...)> [with auto:1 = void (MyClass::*)(int); Function = void (MyClass::*)(int); Args = {MyClass*, int}; std::atomic_bool = std::atomic<bool>]’:
/usr/local/include/c++/6.3.0/type_traits:2481:26:   required by substitution of ‘template<class _Fn, class ... _Args> static std::__result_of_success<decltype (declval<_Fn>()((declval<_Args>)()...)), std::__invoke_other> std::__result_of_other_impl::_S_test(int) [with _Fn = InterruptibleThread::InterruptibleThread(Function&&, Args&& ...) [with Function = void (MyClass::*)(int); Args = {MyClass*, int}]::<lambda(std::atomic_bool&, std::atomic_bool&, auto:1&&, MyClass*&&, int&&)>; _Args = {std::reference_wrapper<std::atomic<bool> >, std::reference_wrapper<std::atomic<bool> >, void (MyClass::*)(int), MyClass*, int}]’
/usr/local/include/c++/6.3.0/type_traits:2492:55:   required from ‘struct std::__result_of_impl<false, false, InterruptibleThread::InterruptibleThread(Function&&, Args&& ...) [with Function = void (MyClass::*)(int); Args = {MyClass*, int}]::<lambda(std::atomic_bool&, std::atomic_bool&, auto:1&&, MyClass*&&, int&&)>, std::reference_wrapper<std::atomic<bool> >, std::reference_wrapper<std::atomic<bool> >, void (MyClass::*)(int), MyClass*, int>’
/usr/local/include/c++/6.3.0/type_traits:2496:12:   required from ‘class std::result_of<InterruptibleThread::InterruptibleThread(Function&&, Args&& ...) [with Function = void (MyClass::*)(int); Args = {MyClass*, int}]::<lambda(std::atomic_bool&, std::atomic_bool&, auto:1&&, MyClass*&&, int&&)>(std::reference_wrapper<std::atomic<bool> >, std::reference_wrapper<std::atomic<bool> >, void (MyClass::*)(int), MyClass*, int)>’
/usr/local/include/c++/6.3.0/functional:1365:61:   required from ‘struct std::_Bind_simple<InterruptibleThread::InterruptibleThread(Function&&, Args&& ...) [with Function = void (MyClass::*)(int); Args = {MyClass*, int}]::<lambda(std::atomic_bool&, std::atomic_bool&, auto:1&&, MyClass*&&, int&&)>(std::reference_wrapper<std::atomic<bool> >, std::reference_wrapper<std::atomic<bool> >, void (MyClass::*)(int), MyClass*, int)>’
/usr/local/include/c++/6.3.0/thread:137:26:   required from ‘std::thread::thread(_Callable&&, _Args&& ...) [with _Callable = InterruptibleThread::InterruptibleThread(Function&&, Args&& ...) [with Function = void (MyClass::*)(int); Args = {MyClass*, int}]::<lambda(std::atomic_bool&, std::atomic_bool&, auto:1&&, MyClass*&&, int&&)>; _Args = {std::reference_wrapper<std::atomic<bool> >, std::reference_wrapper<std::atomic<bool> >, void (MyClass::*)(int), MyClass*, int}]’
test.cpp:41:33:   required from ‘InterruptibleThread::InterruptibleThread(Function&&, Args&& ...) [with Function = void (MyClass::*)(int); Args = {MyClass*, int}]’
test.cpp:111:53:   required from here
test.cpp:36:9: error: must use ‘.*’ or ‘->*’ to call pointer-to-member function in ‘fxn (...)’, e.g. ‘(... ->* fxn) (...)’
      fxn( forward< Args >( args )... );

Current code:

using namespace std;

class InterruptThreadException {};

class InterruptibleThread {
private:
    static thread_local atomic_bool* stopRef;
    static thread_local atomic_bool* pauseRef;
    atomic_bool stopFlag{false};
    atomic_bool pauseFlag{false};
    thread thrd;

public:
    friend void checkForInterrupt( );
    template < typename Function, typename... Args >
    InterruptibleThread( Function&& _fxn, Args&&... _args )
        : thrd(
                []( atomic_bool& sr, atomic_bool& pr, auto&& fxn, Args&&... args ) {
                    stopRef = &sr;
                    pauseRef = &pr;
                    fxn( forward< Args >( args )... );
                },
                ref( stopFlag ),
                ref( pauseFlag ),
                forward< Function >( _fxn ),
                forward< Args >( _args )... ) {
        thrd.detach( );
    }
    bool stopping( ) const {
        return stopFlag.load( );
    }

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

    void pause( ) {
        pauseFlag.store( true );
        cout << "setting pause flag: " << pauseFlag.load( ) << endl;
    }

    void start( ) {
        pauseFlag.store( false );
    }
};

void checkForInterrupt( ) {
    cout << "Pause flag: " << InterruptibleThread::pauseRef->load( ) << endl;
    cout << "Stop flag: " << InterruptibleThread::stopRef->load( ) << endl;
    while ( InterruptibleThread::pauseRef->load( ) ) {
        cout << "Paused\n";
        this_thread::sleep_for( chrono::seconds( 1 ) );
    }
    if ( !InterruptibleThread::stopRef->load( ) ) {
        return;
    }
    throw InterruptThreadException( );
}

thread_local atomic_bool* InterruptibleThread::stopRef = nullptr;
thread_local atomic_bool* InterruptibleThread::pauseRef = nullptr;

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

class MyClass {
private:
    int myInt;
    void setInt( int i ) {
        myInt = i;
    }

public:
    MyClass( ) : myInt( 1 ) {
    }
    void myWork( int i );
    void doWork( );
};

void MyClass::myWork( int i ) {
    setInt( i );
    cout << "myInt value: " << myInt << endl;
}

void MyClass::doWork( ) {
    InterruptibleThread t( &MyClass::myWork, this, 666 );
}

int main( ) {
    MyClass mc;
    mc.doWork( );

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

I tried the compiler suggestion and got an error afterwards related to fold expressions (which afaik are C++17 and I'm not to be using anything beyond 14). Any idea how to get this to work?

I have a working version of this without using lambdas but I'm really curious how to get this to work with the original code.

user1324674
  • 384
  • 1
  • 3
  • 12
  • It looks to me like your compiler isn't happy with the 'static' modifiers in the definition for your 'class interruptiblethread'. Can you take those out? Just to see if it helps? – Dr t Aug 10 '17 at 17:27
  • Are you referring to the static thread_locals? If so, thread_locals are implicitly static anyway, but certain compilers wont compile without the static declaration explicitly (someone correct me if I'm wrong, but mine wont) – user1324674 Aug 10 '17 at 17:45

1 Answers1

2

Pointers to member functions are called using a different syntax than pointers to non-member functions or other functors. Since Function in your case is a void (MyClass::*)(int), you need to call it using the syntax (object.*fxn)(arg) or (objectPtr->*fxn)(arg).

If you've got access to C++17 features, you can use std::invoke to uniformly call different types of callables:

InterruptibleThread( Function&& _fxn, Args&&... _args )
    : thrd(
            []( atomic_bool& sr, atomic_bool& pr, auto&& fxn, auto&&... args ) {
                stopRef = &sr;
                pauseRef = &pr;
                std::invoke(std::move(fxn), std::move(args)...);
            },
            //...

Note: I changed Args&&... args to auto&&... args since Args&&... could cause problems.

If you don't have access to C++17, you can implement invoke yourself fairly easily:

template <typename Callable,
          typename... Args,
          typename = std::enable_if_t<!std::is_member_function_pointer<Callable>::value>>
decltype(auto) invoke(Callable&& c, Args&&... args) {
    return std::forward<Callable>(c)(std::forward<Args>(args)...);
}

template <typename T,
          typename T2,
          typename Ret,
          typename... FuncArgs,
          typename... Args,
          typename = std::enable_if_t<!std::is_pointer<T2>::value>>
decltype(auto) invoke(Ret (T::*c)(FuncArgs...), T2&& t, Args&&... args) {
    return (std::forward<T>(t).*c)(std::forward<Args>(args)...);
}

template <typename T,
          typename T2,
          typename Ret,
          typename... FuncArgs,
          typename... Args>
decltype(auto) invoke(Ret (T::*c)(FuncArgs...), T2* t, Args&&... args) {
    return (t->*c)(std::forward<Args>(args)...);
}

This doesn't do everything that std::invoke does, but it does everything you need it for.

Miles Budnek
  • 28,216
  • 2
  • 35
  • 52
  • First of all thank you very much, this solved the general problem. I want to expand on this a little bit as I am using future/promises with my threading and originally was calling move(promise) when creating the thread. This new version using your invoke suggestion causes the move to be called at the wrong time, ultimately breaking the program if a promise is passed as an argument. Would you happen to know the reason for this? How come calling move in the lambda doesn't take care of this problem? – user1324674 Aug 10 '17 at 18:32
  • @user1324674 Edited to separate the member function arg types and the arg types provided. Having them be the same caused problems deducing the right types when they weren't exactly the same (i.e. if a function took a `const T&` but you provided a `T&&`). That may fix your problem; if not you'll need to post your actual code that's causing you problems. – Miles Budnek Aug 10 '17 at 18:51
  • here's the code I'm using to test (its similar to my actual app): https://gist.github.com/anonymous/7287c1a542963ccee32b147bb850b856 line 140 I call InterruptibleThread and pass it a promise. Normally when calling thread here i would call move. I modified the invokes as you have but it didn't solve the issue (the error looks the same) – user1324674 Aug 10 '17 at 18:57
  • @user1324674 So why aren't you calling `std::move`? `std::promise` is not copyable, so passing an lvalue reference to one to `std::thread`'s constructor will not work. Nothing about your `InterruptibleThread` changes that. – Miles Budnek Aug 10 '17 at 20:11
  • Sorry I'm confused, am I supposed to be calling std::move inside of the InterruptibleThread call on line 140? – user1324674 Aug 10 '17 at 20:20
  • @user1324674 Yes, the same as if you were using `std::thread` directly. That value just gets `std::forward`ed on to `std::thread`'s constructor. If you want it to be moved into the thread's internal storage instead of copied, it needs to be an rvalue. See [What are move semantics](https://stackoverflow.com/questions/3106110/what-are-move-semantics) for an in-depth explanation of why you need `std::move` here. – Miles Budnek Aug 10 '17 at 20:22
  • Wow, I don't know why I didn't think of trying that. Thanks again, I've got a lot of things to look up now haha – user1324674 Aug 10 '17 at 20:29