Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

initial review of c++ <chrono> #2

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open

Conversation

gnzlbg
Copy link

@gnzlbg gnzlbg commented May 15, 2018

Rendered.


TL;DR: <chrono> in Rust would look like:

pub trait Clock {
    const STEADY: bool;
    const REAL_TIME: bool;
    type TP: TimePoint;
    fn now(&self) -> Self::TP;
}

pub trait Duration
    :  for<O: Duration> Add<O> + Sub<O> + Mul<O> + Div<O> + ... 
    + Add<Self::Rep> + Sub<Self::Rep> + ... {
    type Rep: Num;
    const Period: (u64,u64);
    const ZERO: Self;
    const MIN: Self;
    const MAX: Self;
    fn count(&self) -> Self::Rep;
    fn floor(&self) -> Self;
    fn ceil(&self) -> Self;
    fn round(&self) -> Self;
    const fn from_rep(r: Self::Rep) -> Self;
}

pub trait SignedDuration: Duration
  where <Self as Duration>::Rep: SignedNum
{
    fn abs(&self) -> Self;
}

pub trait DurationCast<O: Duration>: Duration {
    fn duration_cast(self) -> O;
}
impl<T: Duration, U: Duration> DurationCast<U> for T { ... }

pub trait TimePoint
    : Add<RHS=Self::Duration,Output=Self> + Sub<RHS=Self::Duration,Output=Self>
    + for<T: TimePoint> Add<RHS=T,Output=CommonTimePoint<T,U>> + ...
{
    type Clock: Clock;
    type Duration: Duration;
    const MIN: Self;
    const MAX: Self;
    fn time_since_epoch() -> Self::Duration;
}

Lack of const trait methods could be worked around a bit, but the recursive trait declarations would be painful to workaround (one could add blanket impls). The real problem is implementing these traits. Even with const generics we would still need specialization, maybe with the lattice rule, and negative reasoning, to implement some of the traits. For example, we might want to add a generic implementation of From for all durations:

impl<T: Duration, U: Duration> impl From<U> for T { ... }

which would conflict with the reflexive blanket impl in std::covert for the case U=T so we would need to at least add a where U != T clause there.

@gnzlbg
Copy link
Author

gnzlbg commented May 15, 2018

@HowardHinnant I tried to provide an overview of the design of <chrono> here and have one question a couple of questions:

  • It seems that there are multiple different types representing time points, chrono::time_point which contains just a duration, the ones in <date> with serial layout (sys_days,sys_seconds) and field layout (year_month_day and friends) and the ones in <tz> like zoned_time. Is that correct or am I misunderstanding and these types are not time points? Looks like a TimePoint concept might not buy much, but is there a guide somewhere about what exactly is required to implement a new "time point" type so that it interoperates with the rest of the libraries?

  • I have skimmed in the review over common_type, but I was wondering what kind of errors does one get when trying to cast different duration types or adding incompatible time_points. I can imagine that if a common_type cannot be found, the errors can get really messy really quick. Does the library do something to defend against this? (e.g. static_assert that a common type exists or similar?).

  • Some of the ratios provided at the extreme use really big numbers (e.g. std::ratio<1, 1000000000000000000000000>). Do C++ compilers perform computations with these at compile-time using "big ints" or how does this work ?

@HowardHinnant
Copy link

This library is best understood as something that evolved over the past decade. It began with just chronological types and arithmetic and calendrical types/arithmetic have just recently been designed and are now in the draft C++20 working paper.

  1. time_point is a chronological type. It represents a point in time, and can have any regular precision. Something as fine as attoseconds or as coarse as exaseconds. A time_point with a precision of a day is the bridge that links chronological types with calendrical types.

One can think of calendrical types such as year_month_day as time points, but they really don't have the same API as a time_point. They are time-point-like in the sense that a year_month_daycan be converted to a day-precision time_point and back, with no loss of information.

The calendar serves to simply give memorable names to each value of sys_days (sys_days is just a count of days under the hood). For example according to sys_days today is day number 17666. But humans find such nomenclature confusing and much prefer a name like 2018-05-15. By definition there is only one interpretation for sys_days (a Rosetta stone of calendars if you will). But humans have many different calendars. However only the civil (Gregorian) calendar is proposed for standardization in C++20. The other calendars on my github site are examples of user-written calendars.

To implement a "new" time_point, one has to first implement a new clock. Then a time_point is simply std::chrono::time_point<MyClock, _any_duration_>.

To implement a new calendar, the only real requirement is that it have a conversion to and from sys_days, which is the same thing as time_point<system_clock, days>.

  1. About the only time you can get an error with the common_type of two durations is if there is a compile-time overflow in the computation of the period of the resultant duration. It took me a while to actually come up with something that would overflow, but here's an example:
#include <chrono>

int
main()
{
    using namespace std::chrono;
    using namespace std;
    using X = duration<int, ratio_divide<pico, ratio<3>>>;
    using Y = duration<long long, ratio_divide<atto, ratio<5>>>;
    using ct = common_type_t<X, Y>;
}

The result is pretty ugly and will vary with implementation:

/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1/ratio:121:71: error: in-class
      initializer for static data member is not a constant expression
    static const intmax_t value = _Xp / __static_gcd<_Xp, _Yp>::value * _Yp;
                                  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1/ratio:500:19: note: in instantiation
      of template class 'std::__1::__static_lcm<3000000000000, 5000000000000000000>' requested here
                  __static_lcm<_R1::den, _R2::den>::value> type;
                  ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1/chrono:345:39: note: in instantiation
      of template class 'std::__1::__ratio_gcd<std::__1::ratio<1, 3000000000000>, std::__1::ratio<1, 5000000000000000000> >' requested
      here
                             typename __ratio_gcd<_Period1, _Period2>::type> type;
                                      ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1/type_traits:2078:25: note: in
      instantiation of template class 'std::__1::common_type<std::__1::chrono::duration<int, std::__1::ratio<1, 3000000000000> >,
      std::__1::chrono::duration<long long, std::__1::ratio<1, 5000000000000000000> > >' requested here
template <class ..._Tp> using common_type_t = typename common_type<_Tp...>::type;
                        ^
test.cpp:12:16: note: in instantiation of template type alias 'common_type_t' requested here
    using ct = common_type_t<exaseconds, attoseconds>;
               ^
  1. The spec is set up to only require 64 bit signed integral computation (both at compile-time and run-time). Support for yocto is optional. In practice it would be provided by a 128bit integral type, but I'm not aware of any compiler/library that does so, nor any customer requesting it.

@gnzlbg
Copy link
Author

gnzlbg commented May 15, 2018

@HowardHinnant thanks for the quick answers!

There was an SO question/answer stating that if a clock is not "realtime" it might not be advanced when a thread sleeps. Is that correct? If so, what is the associated thread of a clock and what does that mean for the std library clocks? (when are the std library clocks created/destroyed?)

@HowardHinnant
Copy link

From the C++ spec:

Objects of type system_clock represent wall clock time from the system-wide realtime clock.

Objects of class steady_clock represent clocks for which values of time_point never decrease as physical time advances and for which values of time_point advance at a steady rate relative to real time. That is, the clock may not be adjusted.

<chrono> has no concept of an associated thread of a clock. The system-supplied clocks have no "instance state" and are accessible during the duration of the program. They do not have destructors registered with the atexit chain. The system-supplied clocks have a specification that relates them to "realtime". How well the implementation adheres to "realtime" is a QOI issue (e.g. there is no specification for the clock to keep atomic-quality time).

If the user writes a custom clock that does not advance when a thread sleeps, and then does a timed-sleep using that clock, that thread may never wake (it would depend on the details of the user-written clock).

@gnzlbg
Copy link
Author

gnzlbg commented May 15, 2018

Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants