0

As stated in an answer to this question, std::invoke handles calling not just of simple functions but also other callable types.

Unfortunately I am currently constrained to C++14 - so does anyone know of a C++14 compatible alternative?

My motivation:

My actual problem is that I am trying to write a simple wrapper for std::thread where I want to wrap the call of the passed function f with info like isRunning, which will be set to true before calling f(); and false afterwards.

One possible way I found, was to pass a member-method bound via std::bind(&Class::method, classInstance) - this however breaks the API, as this is something the user of my Wrapper class would have to do.

In case anyone is interested, here is my code:

#include <atomic>
#include <chrono>
#include <condition_variable>
#include <functional>
#include <memory>
#include <mutex>
#include <thread>


class ThreadPeriodic : public std::thread
{

protected:

    struct Settings
    {
        Settings(std::chrono::nanoseconds const periodDuration)
            : isRunning(false)
            , stopped(false)
            , periodDuration(periodDuration)
        {
        }

        volatile std::atomic<bool> isRunning;
        std::mutex mutexStop;
        std::condition_variable conditionVariableStop;
        volatile std::atomic<bool> stopped;
        std::chrono::nanoseconds const periodDuration;
    };

    std::shared_ptr<Settings> settings_;

    template< class Function, class... Args >
    class WrapperClass_
    {
        WrapperClass_() = delete;
    public:
        // https://stackoverflow.com/questions/34731367/how-to-pass-variadic-args-to-a-stdthread
        static void wrapperMethod(std::shared_ptr<Settings> settings,
                                  typename std::decay<Function>::type&& f,
                                  typename std::decay<Args>::type&&... args)
        {
            settings->isRunning = true;

            std::chrono::steady_clock::time_point nextPeriod = std::chrono::steady_clock::now();

            bool stopped = settings->stopped.load();
            while (!stopped)
            {
                try
                {
                    f(std::forward<Args>(args)...);
                }
                catch (...)
                {
                    // allthough this should never happen...
                    settings->isRunning = false;
                    throw;
                }

                nextPeriod += settings->periodDuration;
                std::unique_lock<std::mutex> lock(settings->mutexStop);
                stopped = settings->conditionVariableStop.wait_until(lock, nextPeriod, [settings](){return settings->stopped;});
            }

            settings->isRunning = false;
        }
    };

public:

    ThreadPeriodic() noexcept
    {
    }

    ThreadPeriodic(ThreadPeriodic && other) noexcept
    {
        operator=(std::move(other));
    }

    template< class Function, class... Args >
    explicit ThreadPeriodic(std::chrono::nanoseconds const periodDuration, Function&& f, Args&&... args)
        : settings_(std::make_shared<Settings>(periodDuration))
    {
        std::thread::operator=(std::thread(ThreadPeriodic::WrapperClass_<Function, Args...>::wrapperMethod,
                                           settings_,
                                           std::forward<Function>(f),
                                           std::forward<Args>(args)...));
    }

    template< class Function, class... Args >
    explicit ThreadPeriodic(Function&& f, Args&&... args)
        : ThreadPeriodic(std::chrono::nanoseconds(0), std::forward<Function>(f), std::forward<Args>(args)...)
    {
    }

    template< class Rep, class Period, class Function, class... Args >
    explicit ThreadPeriodic(std::chrono::duration<Rep, Period> const periodDuration, Function&& f, Args&&... args)
        : ThreadPeriodic(std::chrono::duration_cast<std::chrono::nanoseconds>(periodDuration), std::forward<Function>(f), std::forward<Args>(args)...)
    {
    }

    ThreadPeriodic( const ThreadPeriodic& ) = delete;


    ThreadPeriodic& operator=( ThreadPeriodic&& other ) noexcept
    {
        std::thread::operator=(std::move(other));
        settings_ = std::move(other.settings_);
        return *this;
    }


    bool isRunning() const
    {
        std::shared_ptr<Settings> settings = settings_;
        return static_cast<bool>(settings) ? settings->isRunning.load() : false;
    }

    bool isStarted() const
    {
        std::shared_ptr<Settings> settings = settings_;
        return static_cast<bool>(settings) && !settings->stopped;
    }

    bool isStopped() const
    {
        std::shared_ptr<Settings> settings = settings_;
        return static_cast<bool>(settings) ? settings->stopped.load() : true;
    }

    // If joinNow is false, join() shall be called later on manually, as to ensure
    // the thread is actually joined as it should.
    void stop(bool const joinNow = true)
    {
        std::shared_ptr<Settings> settings = settings_;
        if (!static_cast<bool>(settings))
        {
            throw std::logic_error("ThreadPeriodic::stop: this instance does not represent a thread.");
        }
        else if (settings->stopped)
        {
            throw std::logic_error("ThreadPeriodic::stop: this instance is already stopped.");
        }
        else
        {
            {
                std::unique_lock<std::mutex> lock(settings->mutexStop);
                settings->stopped = true;
            }
            settings->conditionVariableStop.notify_all();

            if (joinNow && joinable())
            {
                join();
            }
        }

    }


};

And if anyone wants to test it, here is a basic program:

#include <iostream>

class TestClass
{
public:
    explicit TestClass(int const start = 0)
        : start(start)
    {
    }

    void printNumber(int const step = 1)
    {
        static int number = start;
        std::cout << number << std::endl;
        number += step;
    }

    int start;
};

int main()
{
    TestClass testInstance(0);
    
    // ThreadPeriodic thread(std::chrono::milliseconds(500), &TestClass::printNumber, testInstance, 3);
    ThreadPeriodic thread(std::chrono::milliseconds(500), std::bind(&TestClass::printNumber, testInstance, std::placeholders::_1), 3);
        std::this_thread::sleep_for(std::chrono::seconds(2));
        thread.stop();

    return 0;
}
Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • If there were such an alternative, they wouldn't have needed to add `std::invoke` to C++17. – Nicol Bolas Jan 29 '22 at 14:41
  • 3
    If you look at the [cppreference page](https://en.cppreference.com/w/cpp/utility/functional/invoke), there's a "possible implementation". Usually you can just take it, sometimes with small modifications, and use that in your code. – JHBonarius Jan 29 '22 at 14:45
  • is std::thread designed to inherit from it? a composition of timed_mutex, thread and atomic_bool shoul be enough. but there are scheduling libs for this kind of task – fiorentinoing Jan 29 '22 at 22:42

0 Answers0