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):
using rep = double;
// if you only ever advance the same amount of time each time,
// you could omit the parameter and just increment that much.
// or whatever it really is
static constexpr rep const simulation_timestep = 0.1f;
ticks += simulation_timestep;
// aka "instance's now()"
rep current_time() const
Then we can make a straightforward wrapper template that can allow for a distinct type for each necessary instance:
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
// 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<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.