jeudi 3 décembre 2020

generic std-like chrono-independent template stopwatch

I had a simple stopwatch class that was basically using std::chrono::steady_clock::now() - this->start_ in its implementation for measuring elapsed time. I have decided to upgrade it and make fully generic, std-compliant, not depending on a particular clock type, so user can specify his own clock type. I also had a template stopwatch::operator DurationType() const for convenience, to convert to a specific duration type, for instance:

std::chrono::milliseconds msecs = stopwatch;

But there is one problem: I can't get rid of a chrono header since I am using its duration_cast.

I have come up with 2 ideas. One of them works but not very convenient, and the other does not work at all and is pretty ugly.

// stopwatch.h
#pragma once

// #include <chrono>

namespace eld
{
    template <typename ClockType,
            typename DurationCaster>
    class stopwatch
    {
    public:

        using clock_type = ClockType;
        using duration = typename clock_type::duration;

        stopwatch()
             : start_(clock_type::now())
        {}

        void reset()
        {
            start_ = clock_type::now();
        }

        typename clock_type::duration result() const
        {
            return clock_type::now() - start_;
        }

        template<typename Duration>
        Duration result() const
        {
            // return std::chrono::duration_cast<Duration>(result()); // TODO: remove this
            return DurationCaster()(Duration(), result());
        }

        template<typename Duration>
        operator Duration() const
        {
            return result<Duration>();
        }

        // This is ugly and does not work
//        template <typename Caster>
//        auto operator()(Caster caster) -> decltype(Caster::operator()(typename clock_type::time_point))
//        {
//            return caster(result());
//        }

    private:
        typename clock_type::time_point start_;
    };

}

// dummy.cpp

#include <stopwatch/stopwatch.h>

#include <iostream>
#include <chrono>
#include <thread>

// this is inconvenient
struct DurationCaster
{
    template<typename ToDuration, typename FromDuration>
    constexpr ToDuration operator()(ToDuration, FromDuration fromDuration) const
    {
        return std::chrono::duration_cast<ToDuration>(fromDuration);
    }
};

int main()
{

    eld::stopwatch<std::chrono::steady_clock, DurationCaster> steadyWatch{};

    std::this_thread::sleep_for(std::chrono::milliseconds(20));

    std::chrono::nanoseconds nanoseconds = steadyWatch.result();
    std::cout << nanoseconds.count() << std::endl;

    std::chrono::milliseconds milliseconds = steadyWatch;
    std::cout << milliseconds.count() << std::endl;

    // This is ugly and does not work
//    milliseconds = steadyWatch([](typename decltype(steadyWatch)::duration from)
//            {
//        return std::chrono::duration_cast<std::chrono::milliseconds>(from);
//            });

    return 0;
}

Whilst the first option works despite its inconvenience, I want to know if I can get rid of std::chrono::duration_cast in my header in a more elegant way. Or is this as good as it gets?

P.S.: What's wrong with my second approach? Why can't I compile it?

Aucun commentaire:

Enregistrer un commentaire