0

I would like to use std::chrono::time_point in some simulation software I am writing. I would like to take advantage of features like the time point arithmetic functions. However, I run into a snag. I need to define a clock to use with my time points, and the spec indicates that clocks have a now function such that for a clock C, the expression C::now() "Returns a time_point object representing the current point in time" [time.chrono.req]. At this point I must deviate from the spec, because such a now function doesn't have enough information to return a time point. It would need a reference to my simulated time, and I may have multiple simulations going in parallel at different simulated times.

I don't need the now functionality on the clock. I have other ways of getting access to the correct current time. And I don't need these clocks to work with the wait_until functions in the library (I looked through the spec for all references to now()). But I also don't want to have a malformed clock, for fear of undefined behavior creeping in.

What is the minimum functionality needed for now() to satisfy the language of Clock in C++11?

5
  • Why do you need your own clock class? I guess the intented usage of time_point is in conjunction with one of the clocks defined in <chrono>, like system_clock or utc_clock or high_resolution_clock?
    – Doc Brown
    Sep 24 at 14:22
  • @DocBrown I'd like to take advantage of all the clever work done in getting time_point to work just right (floating point behaviors, ratios, etc.). The only behavior I find undesirable is the behavior of now. That being said, I do believe the chrono clock concept was designed to be extended beyond the built-in clocks. For instance, it would be very natural to create a clock which reads from a hardware device, such as firmware on an embedded platform.
    – Cort Ammon
    Sep 24 at 16:36
  • If you are just using them time_points, you can pick an arbitrary clock with the desired duration
    – Caleth
    Sep 25 at 8:24
  • 1
    "I'd like to take advantage of all the clever work done in getting time_point to work just right" - yes, but why do you need your own clock class for this? Just you can provide your own clock class doesn't mean you have to.
    – Doc Brown
    Sep 25 at 14:53
  • @DocBrown I see what you're saying. In my case, I am interested in implementing time conversions not present in the normal clocks. For example, the subtle difference between TAI and TDB time systems. std::chrono already has an excellent way of managing time points, so I'd like to implement a TdbClock and TaiClock
    – Cort Ammon
    Sep 30 at 1:53

2 Answers 2

0

If you know at compile time that you won't need more than some reasonably small number of them, you may be able to use templates to generate distinct types for each clock you use, which is not "more minimal", but is perhaps more consistent with the way users of Clocks expect them to work.

You already have some mechanism for tracking simulation time. For simplicity, let's assume it looks something like this (though you might be using atomics or other synchronization if multiple threads are using a clock):

struct SimClockImpl
{
    public:
    using rep = double;

    private:
    rep ticks;

    public:

    SimClockImpl()
    : ticks(0)
    {}

    // if you only ever advance the same amount of time each time,
    // you could omit the parameter and just increment that much.
    void advance_time() 
    {
       // or whatever it really is
       static constexpr rep const simulation_timestep = 0.1f;
       ticks += simulation_timestep;
    }

    // aka "instance's now()"
    rep current_time() const
    {
        return ticks;
    }
};

Then we can make a straightforward wrapper template that can allow for a distinct type for each necessary instance:

template<std::size_t>
struct SimClock
{
    public:
    using rep = SimClockImpl::rep;
    using period = std::ratio<1>;
    using duration = std::chrono::duration<rep, period> ;
    using time_point = std::chrono::time_point<SimClock, duration> ;

    static constexpr bool is_steady = false;

    static time_point now() noexcept
    {
        return time_point(duration(distinct_impl.current_time()));
    }

    // note: this inline variable initialization requires c++17 or later
    inline static SimClockImpl distinct_impl = SimClockImpl();
};

At which point, you can have distinct clock types with the same underlying implementation, which each return their own "now": SimClock<0>, SimClock<1>, SimClock<2>, ..., though you may need to watch out if you try to initialize some other static variable using those timers in your program.

I don't think is_steady should be true for your simulation clocks; cppreference's description of the clock requirements suggests that is_steady doesn't only mean that each call to now() gives a time_point >= the time_point from the previous call to now(), but also that the duration between those time_points is meant to be approximately the elapsed real time.

More officially, the draft of the c++17 standard instead phrases this as "the time between clock ticks is constant", and this language is the same in the draft of the c++20 standard. Though it doesn't specifically say "real time", I would guess this usage of "time" is meant to be "real time" since "time as the clock understands it" is "clock ticks" and incrementing clock ticks would always be constant (at least for integers, when not overflowing) so that would be kind of a silly thing to say.

The standard drafts further indicate that now() should not throw exceptions, and that the returned value should be "a time_point object representing the current point in time." I don't see mention of what problems might come up if it doesn't (for example by failing to compile if it would be called), but it might be better to follow the principle of least surprise here if you reasonably can.

1

As an example of what I'm talking about, and an initial answer to my own question, this is the best I have come up with so far. I have what I believe is a valid now() function, except it has a static_assert that will always fail, to notify me that someone is using this function (and then I can figure out why):

struct SimClock
{
    typedef double rep;
    typedef std::ratio<1> period;

    typedef std::chrono::duration<rep, period> duration;
    typedef std::chrono::time_point<SimClock, duration> time_point;

    static constexpr bool is_steady = true;

    template <bool Dummy = false>
    static constexpr time_point now()
    {
        static_assert(Dummy, "This clock has no meaningful now()");
        return time_point(duration(0));
    }
};

Please let me know in the comments if you think this isn't actually a valid clock class, or post an answer if you think there's a more minimal implementation of now() possible.

1
  • That looks viable to me - as you say, the now() should not actually be called, but it does have to exist to satisfy is_clock(). It would also be viable without the assert. Use of double may not be a good idea - I think you would be better with uint64_t.
    – pjc50
    Sep 26 at 10:10

Not the answer you're looking for? Browse other questions tagged or ask your own question.