A time point knows its clock¶
Level: 201 · working knowledge
One line: system_clock::now() - steady_clock::now() does not compile, because a time_point carries its clock in its type — and the refusal is the point: the two clocks count from different moments, so their difference would be a number with a unit and no meaning.
#include <chrono>
auto start = std::chrono::steady_clock::now();
// ... the work ...
auto elapsed = std::chrono::steady_clock::now() - start; // two points on ONE clock: a duration
The slide¶
The talk's Type safety slide (slide source ↗) shows three lines. The slide hides two more — the #include, and the line that makes sc mean std::chrono — so here is the whole file, with the third line commented out:
#include <chrono>
namespace sc = std::chrono; // the line the slide hides
int main() {
auto t1 = sc::steady_clock::now(); // a time_point on the steady clock
auto t2 = sc::system_clock::now(); // a time_point on the system clock
// auto diff = t2 - t1;
}
Uncomment line 7 — auto diff = t2 - t1; — and GCC refuses:
slide.cpp: In function 'int main()':
slide.cpp:7:20: error: no match for 'operator-' (operand types are 'std::chrono::time_point<std::chrono::_V2::system_clock, std::chrono::duration<long int, std::ratio<1, 1000000000> > >' and 'std::chrono::time_point<std::chrono::_V2::steady_clock, std::chrono::duration<long int, std::ratio<1, 1000000000> > >')
7 | auto diff = t2 - t1;
| ~~ ^ ~~
| | |
| | time_point<std::chrono::_V2::steady_clock,[...]>
| time_point<std::chrono::_V2::system_clock,[...]>
Reading the error¶
no match for 'operator-'— GCC looked for a-that accepts these two operand types and found none.- The two operand types. The left operand,
t2, is atime_point<system_clock, duration<long, nano>>; the right,t1, is atime_point<steady_clock, duration<long, nano>>. Under the~~ ^ ~~, GCC names each operand again and writes[...]for the second template argument, because it is the same on both sides — so the clock is the only difference left on the screen. The slide trims even the[...]. _V2is an inline namespace libstdc++ puts its clocks in, so that a later version can redefine them while old binaries keep the old definitions — its source comment says the wrapping gives the current clocks unique mangled names (bits/chrono.h ↗). Readstd::chrono::_V2::steady_clockasstd::chrono::steady_clock; Clang hides inline namespaces and prints it that way.- Why there is no match. The standard declares the subtraction of two time points with one
Clockparameter for both operands (time.point.nonmember ↗):
template<class Clock, class Duration1, class Duration2>
constexpr common_type_t<Duration1, Duration2>
operator-(const time_point<Clock, Duration1>& lhs, const time_point<Clock, Duration2>& rhs);
The durations may differ — that is why there are two of them — but there is only one clock. Deduction gets system_clock from the left operand and steady_clock from the right, cannot make Clock both, and discards the candidate. The rest of GCC's 51 lines are other operator-s — for reverse_iterator, move_iterator and so on — failing for the same reason.
Why refusing is right¶
The two clocks count from different moments. system_clock measures "time since 1970-01-01 00:00:00 UTC excluding leap seconds" (time.clock.system ↗); steady_clock's epoch is unspecified. You can see what the refused subtraction would have meant by removing the clocks first — time_since_epoch() returns a bare duration, and bare durations subtract whatever clock they came from:
auto nonsense = t2.time_since_epoch() - t1.time_since_epoch(); // compiles
std::cout << std::chrono::duration_cast<std::chrono::days>(nonsense); // 20699d on this Mac
Apple clang 21 / libc++ 21, this Mac 20699d
GCC 13.4 / libstdc++, Docker's Linux VM 20702d
The number is not random, which is what makes it instructive. Time since 1970 minus time since boot is the moment the machine booted, counted in days from 1970: day 20,699 is 2026-09-03, and sysctl kern.boottime says this Mac booted on 3 September 2026 at 17:31. Docker's VM booted three days later. As a duration, it means nothing — and computing it is exactly what the type refused to let you do by accident.
What compiles and what does not¶
Verified output of a_time_point_knows_its_clock.cpp — regenerated by tools/run_examples.py and held to the same answer key on libstdc++ and libc++ in CI, never hand-typed.
steady - steady compiles: true
system - system compiles: true
system - steady compiles: false
system < steady compiles: false
steady + steady compiles: false
steady + seconds compiles: true
later - start = 2000000000ns
in seconds = 2s
system.time_since_epoch() - steady.time_since_epoch() compiles: true
subtractable<A, B> is a C++20 concept that asks the compiler whether a - b would compile and answers true or false instead of stopping the build, so the slide's refusal becomes a line of output — the same line on both standard libraries. The six questions are the rules of time-point arithmetic:
| Expression | Compiles? | Gives |
|---|---|---|
steady - steady |
yes | a duration: how far apart |
system - system |
yes | a duration, which may be negative |
system - steady |
no | — different clocks |
system < steady |
no | — comparison has one Clock parameter too |
steady + steady |
no | — adding two moments means nothing, even on one clock |
steady + seconds |
yes | a time point: a moment, moved |
The last line of the output is the hole in the fence. time_since_epoch() — like .count() on a duration — hands back something with no clock attached, and from there the compiler cannot help you.
When you need both clocks¶
Take both readings, at the same moment, and never subtract across them: measure with the steady_clock pair, and use the system_clock reading only to say when it happened. C++20's clock_cast ↗ converts between clocks that have a defined relationship — system_clock, utc_clock, tai_clock, gps_clock and file_clock — and not steady_clock, which has none:
GCC 13.4, 14.4, 15.2 error: no matching function for call to 'clock_cast<std::chrono::_V2::system_clock>(std::chrono::_V2::steady_clock::time_point)'
Apple clang 21 error: no member named 'clock_cast' in namespace 'std::chrono'
The second line is a different fact: libc++ 21 does not have clock_cast at all yet.
Clang says it differently¶
slide.cpp:7:20: error: invalid operands to binary expression ('time_point' (aka 'time_point<system_clock>') and 'time_point' (aka 'time_point<std::chrono::steady_clock, duration<long long, ratio<1LL, 1000000000LL>>>'))
7 | auto diff = t2 - t1;
| ~~ ^ ~~
The same refusal in other words — invalid operands for no match — followed by 26 notes. The left type is printed as plain time_point<system_clock> because on libc++ that clock's time point uses its default duration, microseconds, and Clang leaves default template arguments out.
If you are coming from another language¶
- Rust. The same refusal from a different mechanism:
InstantandSystemTimeare unrelated types, andSystemTime - Instantis errorE0308. Rust also refuses the subtraction C++ allows,SystemTime - SystemTime, because the wall clock can go backwards and a RustDurationcannot be negative: An Instant is not a SystemTime ↗. - Python. Every clock returns a
float, so nothing is refused:time.time() - time.monotonic()runs, and on this Mac with Python 3.14.7 it printed1788673816.04— the same boot-moment nonsense, in seconds. It lands on 6 September rather than 3 September because Python'smonotonic()stops while the machine sleeps (Three clocks). - ABAP. (Not machine-checked.)
GET RUN TIME FIELDandGET TIME STAMP FIELDboth hand back plain numbers, so subtracting one from the other compiles — the mixed-clock subtraction with nothing to stop it.
See also¶
- Three clocks — the two epochs, measured
- What a clock is —
time_pointis the fourth type every clock defines - A duration is a count and a unit — what the subtraction hands back
- cppreference: time_point ↗