What type to use for a timeout variable in C++?

JohnCand picture JohnCand · Nov 15, 2013 · Viewed 7.5k times · Source

When writing a function in C++ that takes a timeout as one of its arguments, what type should I use for the timeout argument itself?

An example of such a function might be:

void my_function(bool work_really_hard, timeout_type timeout)
{
// Do stuff, until timeout is reached.
}

I have thought about using std::chrono::seconds for timeout_type, but that disallows using any timeout in the sub-second realm.

When using std::chrono::nanoseconds instead, it is cumbersome to specify, say, 10 minutes.

Any way to resolve this in a reasonable way, while keeping the function signature and calls neat and simple?

Answer

Howard Hinnant picture Howard Hinnant · Nov 15, 2013

When using std::chrono::nanoseconds instead, it is cumbersome to specify, say, 10 minutes.

Actually, it doesn't make it cumbersome in the least:

#include <chrono>
#include <iostream>

void my_function(bool work_really_hard, std::chrono::nanoseconds timeout)
{
    std::cout << timeout.count() << '\n';
}

int main()
{
    my_function(true, std::chrono::minutes(10));
}

Output:

600000000000

The only time you'll have trouble with nanoseconds is if you want to pass in something that won't exactly convert to nanoseconds, such as picoseconds, or duration<long, ratio<1, 3>> (1/3 second units).

Update

I intended this answer to be additional information for an already accepted answer that I thought was a good answer (by sehe). sehe recommended a templated solution, which I also consider fine.

If you want to accept any std::chrono::duration, even one that you may have to truncate or round, then going with sehe's deleted answer is the way to go:

template <typename Rep, typename Period>
void my_function(bool work_really_hard, std::chrono::duration<Rep, Period> timeout)
{
    // Do stuff, until timeout is reached.
    std::this_thread::sleep_for(timeout);
}

If for some reason you do not want to deal with templates and/or you are content with having your clients having to specify only units that are exactly convertible to std::chrono:nanoseconds, then using std::chrono:nanoseconds as I show above is also completely acceptable.

All of the std::chrono "pre-defined" units:

hours
minutes
seconds
milliseconds
microseconds
nanoseconds

are implicitly convertible to nanoseconds, and will not involve any truncation or round off error. Overflow will not happen as long as you keep it within the two bright white lines (obscure reference to keeping your car in your own lane). As long as the duration is within +/- 292 years, you don't have to worry about overflow with these pre-defined units.

The std-defined functions such as std::this_thread::sleep_for are templated as sehe suggests for exactly the reason of wanting to be interoperable with every chrono:duration imaginable (e.g. 1/3 of a floating-point femtosecond). It is up to the API designer to decide if they need that much flexibility in their own API.

If I've now managed to confuse you instead of clarify, don't worry too much. If you choose to use nanoseconds, things will either work exactly, with no truncation or round off error, or the client will get a compile time error. There will be no run time error.

void my_function(bool work_really_hard, std::chrono::nanoseconds timeout)
{
    std::cout << timeout.count() << '\n';
}

int
main()
{
    using namespace std;
    using namespace std::chrono;
    typedef duration<double, pico> picoseconds;
    my_function(true, picoseconds(100000.25));
}

test.cpp:15:9: error: no matching function for call to 'my_function'
        my_function(true, picoseconds(100000.25));
        ^~~~~~~~~~~
test.cpp:4:10: note: candidate function not viable: no known conversion from 'duration<double, ratio<[...], 1000000000000>>' to
      'duration<long long, ratio<[...], 1000000000>>' for 2nd argument
    void my_function(bool work_really_hard, std::chrono::nanoseconds timeout)
         ^
1 error generated.

And if the client gets a compile-time error, he can always use duration_cast to work around it:

my_function(true, duration_cast<nanoseconds>(picoseconds(100000.25)));  // Ok

For further details, please see:

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2661.htm

Sweet update to the original code at the top of this answer. In C++1y, which we hope means C++14:

using namespace std::literals;
my_function(true, 10min);  // also ok, is equal to 600000000000 nanoseconds

Question: What would you recommend as a timeout of "infinity" (i.e. don't time out)

I would first try to use an API that didn't take a timeout, and which implied "doesn't time out." For example condition_variable::wait. If I had control over the API, I would create such a signature without a timeout.

Failing that, I would create a series a "large" chrono::durations:

using days = std::chrono::duration
<
    std::int32_t, std::ratio_multiply<std::chrono::hours::period, std::ratio<24>>
>;

using weeks = std::chrono::duration
<
    std::int32_t, std::ratio_multiply<days::period, std::ratio<7>>
>;

using years = std::chrono::duration
<
    std::int32_t, std::ratio_multiply<days::period, std::ratio<146097, 400>>
>;

using months = std::chrono::duration
<
    std::int32_t, std::ratio_divide<years::period, std::ratio<12>>
>;

And then I would use one of these large durations in my call, for example:

std::this_thread::sleep_for(years(3));

I would not try to push_things to the maximum (you might break the underlying assumptions the OS has made for example). Just arbitrarily pick something ridiculously large (like 3 years). That will catch your code reviewer's eye, and likely strike up an informative conversation. :-)

Now available in video: https://www.youtube.com/watch?v=P32hvk8b13M :-)