error: conversion from std::chrono::time_point float to non-scalar type long int requested

Viewed 3113

I have this code that tries to create a new time_point by adding a duration:

// now
std::chrono::system_clock::time_point now = std::chrono::system_clock::now();
// duration
std::chrono::duration<float> duration(1.0f / 60.0f); // float in seconds
// now + duration
std::chrono::system_clock::time_point futureTime = now + duration;

But it gives me this error

error: conversion from 'std::chrono::time_point<std::chrono::_V2::system_clock, std::chrono::duration<float, std::ratio<1, 1000000000> > >' to non-scalar type 'std::chrono::_V2::system_clock::time_point {aka std::chrono::time_point<std::chrono::_V2::system_clock, std::chrono::duration<long int, std::ratio<1, 1000000000> > >}' requested
# it's over there in the distance --->

# ... again, with some manual formatting:
error:
    conversion from
    'std::chrono::time_point<std::chrono::_V2::system_clock,
                             std::chrono::duration<float, 
                                                   std::ratio<1, 1000000000> > >'
    to non-scalar type
    'std::chrono::_V2::system_clock::time_point
     {aka 
     std::chrono::time_point<std::chrono::_V2::system_clock,
                             std::chrono::duration<long int,
                                                   std::ratio<1, 1000000000> > >}'
    requested

After some heavy squinting there's a float in the first one and a long int in the second. Thus now (long int) + duration (float) gives a time_point with some internal floating point duration and I'm presumably asking for it to be stuffed back into the default long int representation.

I'd like to eventually pass this time point to std::condition_variable::wait_until. How can I force the conversion to a std::chrono::system_clock::time_point?

What kind of precision will I lose if I do (i.e. is it storing milliseconds in which case I'll lose some of my 1 / 60)?

If I don't convert it, what's a nice short way of writing the type that now + duration returns (yes I could use auto but for the sake of say passing it to a function)?

2 Answers

The analysis you have done in your question is basically correct. And there are a couple of good ways to make this work.1, 2

Use auto

// now
system_clock::time_point now = system_clock::now();
// duration
duration<float> duration(1.0f / 60.0f); // float in seconds
// now + duration
auto futureTime = now + duration;

The type of futureTime is a time_point<system_clock, duration<float, nano>>. That is, it is a time_point based on system_clock, storing nanoseconds represented by a float.

This type can be used with std::condition_variable::wait_until.

Cast now + duration back to system_clock::time_point:

// now
system_clock::time_point now = system_clock::now();
// duration
duration<float> duration(1.0f / 60.0f); // float in seconds
// now + duration
system_clock::time_point futureTime =
    time_point_cast<system_clock::duration>(now + duration);

time_point_cast<duration> is how you cast time_points. You have to explicitly specify the type of duration you want the resulting time_point to have. The resultant time_point will continue to be based on the same clock. The result will be truncated (rounded towards zero) to the next integral system_clock::duration (nanoseconds on this platform).

This type can be used with std::condition_variable::wait_until.

Round now + duration back to system_clock::time_point:

system_clock::time_point futureTime = round<system_clock::duration>(now + duration);

This is just like the previous solution except this rounds to the nearest integral nanoseconds, and to the even nanoseconds on a tie. std::chrono::round is available in C++17 and later. This will give a result at most 1ns different than time_point_cast.

If you don't have C++17 but still want to use round, feel free to use this one.

This type can be used with std::condition_variable::wait_until.

Represent 1/60 exactly with integral arithmetic

// now
system_clock::time_point now = system_clock::now();
// duration
duration<int, std::ratio<1, 60>> duration{1}; // 1/60 of a second
// now + duration
auto futureTime = now + duration;

It is common for people to say:

It isn't possible to represent 1/60 second precisely.

But you can with <chrono>. :-) duration<int, std::ratio<1, 60>> counts in units of 1/60 of a second using a representation of int. duration{1} is 1/60 of a second. duration{2} is 2/60 of a second. Etc.

auto really comes in handy here. The type of futureTime is: time_point<system_clock, duration<system_clock::rep, ratio<1, 3'000'000'000>>>. Or in English: A time_point based on system_clock using a duration with a representation type of whatever system_clock::time_point uses (typically long long) and a period of 1/3 nanoseconds.

It turns out that 1/3 nanoseconds is the coarsest precision that can exactly represent both system_clock::period (nanoseconds on this platform) and 1/60 of a second.

And <chrono> figures all of this out for you automatically. All you have to do to take advantage of it is use auto.

This type can be used with std::condition_variable::wait_until.

One could also use duration<int, std::ratio<1, 60>> in combination with time_point_cast or round as shown earlier if you wanted to get back to the nanoseconds-based system_clock::time_point.

Summary

Which is best? You will have to decide that based on other factors in your application. Typically what is best is whatever makes the rest of your code more readable.

But you've got options, and they are all good options. And they will all work with std::condition_variable::wait_until.

As you explore your options, if it doesn't compile, that's because <chrono> is catching your errors at compile-time (just like the first one). If it compiles (and if you haven't escaped the type-system with .count() or .time_since_epoch()), then it's working.


1 The answers in this question assume system_clock::duration is nanoseconds as reported in the question. This is only true with gcc, and is different on Windows and macOS.

2 In order to not get lost in verbosity, I'm writing my answers as if there is a:

using namespace std::chrono;

in play. It makes things so much more readable.

std::condition_variable::wait_until looks like this: (cppreference)

template< class Clock, class Duration >
std::cv_status
    wait_until( std::unique_lock<std::mutex>& lock,
                const std::chrono::time_point<Clock, Duration>& timeout_time );

template< class Clock, class Duration, class Pred >    
bool wait_until( std::unique_lock<std::mutex>& lock,
                 const std::chrono::time_point<Clock, Duration>& timeout_time,
                 Pred pred );

You can pass any time_point<C, D> to it. It doesn't have to be system_clock::time_point. And system_clock::time_point is just time_point<system_clock>.

The exact representation of the clocks is implementation-defined, but it has to be an integer type. It isn't possible to represent (1/60)s precisely with an integer as the number and second as the unit.

now + duration, where now is of type time_point<C, D1> and duration is of type duration<R2, P2>, is of type time_point<C, std::common_type_t<duration<R1, P1>, D2>>.

Related