1487. Clock related operations exception specifications conflict

Section: 33.3.3 [thread.thread.this] Status: C++11 Submitter: Switzerland Opened: 2010-08-25 Last modified: 2016-02-10

Priority: Not Prioritized

View all other issues in [thread.thread.this].

View all issues with C++11 status.

Discussion:

Addresses CH-25

Clock related operations are currently not required not to throw. So "Throws: Nothing." is not always true.

[ Resolution proposed by ballot comment: ]

Either require clock related operations not to throw (in 20.10) or change the Throws clauses in 30.3.2. Also possibly add a note that abs_time in the past or negative rel_time is allowed.

[2011-02-10: Howard Hinnant provides a resolution proposal]

[Previous proposed resolution:]

  1. Change the Operational semantics of C1::now() in 23.17.3 [time.clock.req], Table 59 — Clock requirements as follows:

    Table 59 — Clock requirements
    Expression Return type Operational semantics
    C1::now() C1::time_point Returns a time_point object
    representing the current point in time.
    Shall not throw an exception.

[2011-02-19: Daniel comments and suggests an alternative wording]

Imposing the no-throw requirement on C1::now() of any clock time is an overly radical step: It has the indirect consequences that representation types for C1::rep can never by types with dynamic memory managment, e.g. my big_int, which are currently fully supported by the time utilities. Further-on this strong constraint does not even solve the problem described in the issue, because we are still left with the fact that any of the arithmetic operations of C1::rep, C1::duration, and C1::time_point may throw exceptions.

The alternative proposal uses the following strategy: The general Clock requirements remain untouched, but we require that any functions of the library-provided clocks from sub-clause 23.17.7 [time.clock] and their associated types shall not throw exceptions. Second, we replace existing noexcept specifications of functions from Clause 30 that depend on durations, clocks, or time points by wording that clarifies that these functions can only throw, if the operations of user-provided durations, clocks, or time points used as arguments to these functions throw exceptions.

[2011-03-23 Daniel and Peter check and simplify the proposed resolution resulting in this paper]

There is an inherent problem with std::time_point that it doesn't seem to have an equivalent value for ((time_t)-1) that gets returned by C's time() function to signal a problem, e.g., because the underlying hardware is unavailable. After a lot of thinking and checks we came to the resolution that timepoint::max() should be the value to serve as a value signaling errors in cases where we prefer to stick with no-throw conditions. Of-course, user-provided representation types don't need to follow this approach if they prefer exceptions to signal such failures.

the functions now() and from_time_t() can remain noexcept with the solution to return timepoint::max() in case the current time cannot be determined or (time_t)-1 is passed in, respectively.

Based on the previous proposed solution to LWG 1487 we decided that the new TrivialClock requirements should define that now() mustn't throw and return timepoint::max() to signal a problem. That is in line with the C standard where (time_t)-1 signals a problem. Together with a fix to a - we assumed - buggy specifcation in 20.11.3 p2 which uses "happens-before" relationship with something that isn't any action:

2 In Table 59 C1 and C2 denote clock types. t1 and t2 are values returned by C1::now() where the call returning t1 happens before (1.10) the call returning t2 and both of these calls happen before C1::time_point::max().

[2011-03-23 Review with Concurrency group suggested further simplifications and Howard pointed out, that we do not need time_point::max() as a special value.]

also the second "happens before" will be changed to "occurs before" in the english meaning. this is to allow a steady clock to wrap.

Peter updates issue accordingly to discussion.

[Note to the editor: we recommend removal of the following redundant paragraphs in 33.5.4 [thread.condition.condvarany] p. 18 to p. 21, p. 27, p. 28, p. 30, and p. 31 that are defining details for the wait functions that are given by the Effects element. ]

[Note to the editor: we recommend removal of the following redundant paragraphs in 33.5.3 [thread.condition.condvar]: p24-p26, p33-p34, and p36-p37 that are defining details for the wait_for functions. We believe these paragraphs are redundant with respect to the Effects clauses that define semantics based on wait_until. An example of such a specification is the wait() with a predicate. ]

Proposed resolution:

  1. Change p2 in 20.11.3 [time.clock.req] as follows

    2 In Table 59 C1 and C2 denote clock types. t1 and t2 are values returned by C1::now() where the call returning t1 happens before (1.10) the call returning t2 and both of these calls happen occur before C1::time_point::max(). [ Note: This means C1 didn't wrap around between t1 and t2end note ]

  2. Add the following new requirement set at the end of sub-clause 23.17.3 [time.clock.req]: [Comment: This requirement set is intentionally incomplete. The reason for this incompleteness is the based on the fact, that if we would make it right for C++0x, we would end up defining something like a complete ArithmeticLike concept for TC::rep, TC::duration, and TC::time_point. But this looks out-of scope for C++0x to me. The effect is that we essentially do not exactly say, which arithmetic or comparison operations can be used in the time-dependent functions from Clause 30, even though I expect that all declared functions of duration and time_point are well-formed and well-defined. — end comment]

    3 [ Note: the relative difference in durations between those reported by a given clock and the SI definition is a measure of the quality of implementation. — end note ]

    ? A type TC meets the TrivialClock requirements if:

    • TC satisfies the Clock requirements (23.17.3 [time.clock.req]),

    • the types TC::rep, TC::duration, and TC::time_point satisfy the requirements of EqualityComparable ( [equalitycomparable]), LessThanComparable ( [lessthancomparable]), DefaultConstructible ( [defaultconstructible]), CopyConstructible ( [copyconstructible]), CopyAssignable ( [copyassignable]), Destructible ( [destructible]), and of numeric types ([numeric.requirements]) [Note: This means in particular, that operations of these types will not throw exceptions — end note ],

    • lvalues of the types TC::rep, TC::duration, and TC::time_point are swappable (20.5.3.2 [swappable.requirements]),

    • the function TC::now() does not throw exceptions, and

    • the type TC::time_point::clock meets the TrivialClock requirements, recursively.

  3. Modify 23.17.7 [time.clock] p. 1 as follows:

    1 - The types defined in this subclause shall satisfy the TrivialClock requirements (20.11.1).

  4. Modify 23.17.7.1 [time.clock.system] p. 1, class system_clock synopsis, as follows:

    class system_clock {
    public:
      typedef see below rep;
      typedef ratio<unspecified , unspecified > period;
      typedef chrono::duration<rep, period> duration;
      typedef chrono::time_point<system_clock> time_point;
      static const bool is_monotonic is_steady = unspecified;
      static time_point now() noexcept;
      // Map to C API
      static time_t to_time_t (const time_point& t) noexcept;
      static time_point from_time_t(time_t t) noexcept;
    };
    
  5. Modify the prototype declarations in 23.17.7.1 [time.clock.system] p. 3 + p. 4 as indicated (This edit also fixes the miss of the static specifier in these prototype declarations):

    static time_t to_time_t(const time_point& t) noexcept;
    
    static time_point from_time_t(time_t t) noexcept;
    
  6. Modify 23.17.7.2 [time.clock.steady] p. 1, class steady_clock synopsis, as follows:

    class steady_clock {
    public:
      typedef unspecified rep;
      typedef ratio<unspecified , unspecified > period;
      typedef chrono::duration<rep, period> duration;
      typedef chrono::time_point<unspecified, duration> time_point;
      static const bool is_monotonic is_steady = true;
    
      static time_point now() noexcept;
    };
    
  7. Modify 23.17.7.3 [time.clock.hires] p. 1, class high_resolution_clock synopsis, as follows:

    class high_resolution_clock {
    public:
      typedef unspecified rep;
      typedef ratio<unspecified , unspecified > period;
      typedef chrono::duration<rep, period> duration;
      typedef chrono::time_point<unspecified, duration> time_point;
      static const bool is_monotonic is_steady = unspecified;
    
      static time_point now() noexcept;
    };
    
  8. Add a new paragraph at the end of 33.2.4 [thread.req.timing]:

    6 The resolution of timing provided by an implementation depends on both operating system and hardware. The finest resolution provided by an implementation is called the native resolution.

    ? Implementation-provided clocks that are used for these functions shall meet the TrivialClock requirements (23.17.3 [time.clock.req]).

  9. Edit the synopsis of 33.3.3 [thread.thread.this] before p. 1. [Note: this duplicates edits also in D/N3267]:

    template <class Clock, class Duration>
    void sleep_until(const chrono::time_point<Clock, Duration>& abs_time) noexcept;
    template <class Rep, class Period>
    void sleep_for(const chrono::duration<Rep, Period>& rel_time) noexcept;
    
  10. Modify the prototype specifications in 33.3.3 [thread.thread.this] before p. 4 and p. 6 and re-add a Throws element following the Synchronization elements at p. 5 and p. 7:

    template <class Clock, class Duration>
    void sleep_until(const chrono::time_point<Clock, Duration>& abs_time) noexcept;
    

    4 - [...]

    5 - Synchronization: None.

    ? - Throws: Nothing if Clock satisfies the TrivialClock requirements (23.17.3 [time.clock.req]) and operations of Duration do not throw exceptions. [Note: Instantiations of time point types and clocks supplied by the implementation as specified in 23.17.7 [time.clock] do not throw exceptions. — end note]

    template <class Rep, class Period>
    void sleep_for(const chrono::duration<Rep, Period>& rel_time) noexcept;
    

    6 [...]

    7 Synchronization: None.

    ? Throws: Nothing if operations of chrono::duration<Rep, Period> do not throw exceptions. [Note: Instantiations of duration types supplied by the implementation as specified in 23.17.7 [time.clock] do not throw exceptions. — end note]

  11. Fix a minor incorrectness in p. 5: Duration types need to compare against duration<>::zero(), not 0:

    3 The expression m.try_lock_for(rel_time) shall be well-formed and have the following semantics:

    [...]

    5 Effects: The function attempts to obtain ownership of the mutex within the relative timeout (30.2.4) specified by rel_time. If the time specified by rel_time is less than or equal to 0rel_time.zero(), the function attempts to obtain ownership without blocking (as if by calling try_lock()). The function shall return within the timeout specified by rel_time only if it has obtained ownership of the mutex object. [ Note: As with try_lock(), there is no guarantee that ownership will be obtained if the lock is available, but implementations are expected to make a strong effort to do so. — end note ]

  12. Modify the class timed_mutex synopsis in 33.4.3.3.1 [thread.timedmutex.class] as indicated: [Note: this duplicates edits also in D/N3267]:

    class timed_mutex {
    public:
      [...]
      template <class Rep, class Period>
        bool try_lock_for(const chrono::duration<Rep, Period>& rel_time) noexcept;
      template <class Clock, class Duration>
        bool try_lock_until(const chrono::time_point<Clock, Duration>& abs_time) noexcept;
      [...]
    };
    
  13. Modify the class recursive_timed_mutex synopsis in 33.4.3.3.2 [thread.timedmutex.recursive] as indicated: [Note: this duplicates edits also in D/N3267]:

    class recursive_timed_mutex {
    public:
      [...]
      template <class Rep, class Period>
        bool try_lock_for(const chrono::duration<Rep, Period>& rel_time) noexcept;
      template <class Clock, class Duration>
        bool try_lock_until(const chrono::time_point<Clock, Duration>& abs_time) noexcept;
      [...]
    };
    
  14. Modify the class template unique_lock synopsis in 33.4.4.3 [thread.lock.unique] as indicated. [Note: this duplicates edits also in D/N3267]:

    template <class Mutex>
    class unique_lock {
    public:
      [...]
      template <class Clock, class Duration>
        unique_lock(mutex_type& m, const chrono::time_point<Clock, Duration>& abs_time) noexcept;
      template <class Rep, class Period>
        unique_lock(mutex_type& m, const chrono::duration<Rep, Period>& rel_time) noexcept;
      [...]
    };
    
  15. Modify the constructor prototypes in 33.4.4.3.1 [thread.lock.unique.cons] before p. 14 and p. 17 [Note: this duplicates edits also in D/N3267]:

    template <class Clock, class Duration>
      unique_lock(mutex_type& m, const chrono::time_point<Clock, Duration>& abs_time) noexcept;
    
    template <class Rep, class Period>
      unique_lock(mutex_type& m, const chrono::duration<Rep, Period>& rel_time) noexcept;