2739. Issue with time_point non-member subtraction with an unsigned duration

Section: 23.17.6.5 [time.point.nonmember] Status: C++17 Submitter: Michael Winterberg Opened: 2016-06-23 Last modified: 2017-07-30

Priority: 0

View all other issues in [time.point.nonmember].

View all issues with C++17 status.

Discussion:

In N4594, 23.17.6.5 [time.point.nonmember], operator-(time_point, duration) is specified as:

template <class Clock, class Duration1, class Rep2, class Period2>
  constexpr time_point<Clock, common_type_t<Duration1, duration<Rep2, Period2>>>
  operator-(const time_point<Clock, Duration1>& lhs, const duration<Rep2, Period2>& rhs);

-3- Returns: lhs + (-rhs).

When Rep2 is an unsigned integral type, the behavior is quite different with arithmetic of the underlying integral types because of the requirement to negate the incoming duration and then add that. It also ends up producing different results than the underlying durations as well as the non-member time_point::operator-=.

Consider this program:

#include <chrono>
#include <iostream>
#include <cstdint>

using namespace std;
using namespace std::chrono;

int main()
{
  const duration<uint32_t> unsignedSeconds{5};

  auto someValue = system_clock::from_time_t(200);
  cout << system_clock::to_time_t(someValue) << '\n';
  cout << system_clock::to_time_t(someValue - unsignedSeconds) << '\n';
  someValue -= unsignedSeconds;
  cout << system_clock::to_time_t(someValue) << '\n';

  std::chrono::seconds signedDur{200};
  cout << signedDur.count() << '\n';
  cout << (signedDur - unsignedSeconds).count() << '\n';
  signedDur -= unsignedSeconds;
  cout << signedDur.count() << '\n';
}

The goal of the program is to compare the behavior of time_point non-member operator-, time_point member operator-=, duration non-member operator-, and duration member operator-= with basically the same inputs.

libc++ produces this output, which appears mandated by the standard:

200
4294967491
195
200
195
195

On the other hand, libstdc++ produces this output, which is what I "intuitively" expect and behaves more consistently:

200
195
195
200
195
195

Given the seemingly brief coverage of durations with unsigned representations in the standard, this seems to be an oversight rather than a deliberate choice for this behavior. Additionally, there may be other "unexpected" behaviors with durations with an unsigned representation, this is just the one that I've come across.

[2016-07 Chicago]

Monday: P0 - tentatively ready

Proposed resolution:

This wording is relative to N4594.

  1. Change 23.17.6.5 [time.point.nonmember] as indicated:

    template <class Clock, class Duration1, class Rep2, class Period2>
      constexpr time_point<Clock, common_type_t<Duration1, duration<Rep2, Period2>>>
      operator-(const time_point<Clock, Duration1>& lhs, const duration<Rep2, Period2>& rhs);
    

    -3- Returns: lhs + (-rhs)CT(lhs.time_since_epoch() - rhs), where CT is the type of the return value.