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_point
s 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.
time_point
is in conjunction with one of the clocks defined in <chrono>, likesystem_clock
orutc_clock
orhigh_resolution_clock
?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.time_point
s, you can pick an arbitrary clock with the desired duration