Title
year_month* arithmetic rejects durations convertible to years
Status
c++20
Section
[time.cal]
Submitter
Tomasz Kamiński

Created on 2019-08-15.00:00:00 last changed 45 months ago

Messages

Date: 2020-02-14.11:24:43

Proposed resolution:

This wording is relative to N4849.

[Drafting note: Suggested wording below assumes that we can add a Constraints: to a signature where the constraint does not apply to a deduced template. We have examples of such constraints in other parts of the WD (e.g. [unique.ptr.single.ctor]/p15, [unique.ptr.single.asgn]/p1). And we have the old form "does not participate …" being used for non-deduced templates in several places as well (e.g. [pairs.pair]/p5).

There are several ways of implementing such a constraint, such as adding a gratuitous template parameter.]

  1. Modify [time.cal.ym.members] as indicated:

    constexpr year_month& operator+=(const months& dm) noexcept;
    

    -?- Constraints: If the argument supplied by the caller for the months parameter is convertible to years, its implicit conversion sequence to years is worse than its implicit conversion sequence to months ([over.ics.rank]).

    -4- Effects: *this = *this + dm.

    […]

    constexpr year_month& operator-=(const months& dm) noexcept;
    

    -?- Constraints: If the argument supplied by the caller for the months parameter is convertible to years, its implicit conversion sequence to years is worse than its implicit conversion sequence to months ([over.ics.rank]).

    -6- Effects: *this = *this - dm.

    […]

  2. Modify [time.cal.ym.nonmembers] as indicated:

    constexpr year_month operator+(const year_month& ym, const months& dm) noexcept;
    

    -?- Constraints: If the argument supplied by the caller for the months parameter is convertible to years, its implicit conversion sequence to years is worse than its implicit conversion sequence to months ([over.ics.rank]).

    -3- Returns: A year_month value z such that z - ym == dm.

    […]

    constexpr year_month operator+(const months& dm, const year_month& ym) noexcept;
    

    -?- Constraints: If the argument supplied by the caller for the months parameter is convertible to years, its implicit conversion sequence to years is worse than its implicit conversion sequence to months ([over.ics.rank]).

    -5- Returns: ym + dm.

    constexpr year_month operator-(const year_month& ym, const months& dm) noexcept;
    

    -?- Constraints: If the argument supplied by the caller for the months parameter is convertible to years, its implicit conversion sequence to years is worse than its implicit conversion sequence to months ([over.ics.rank]).

    -6- Returns: ym + -dm.

  3. Modify [time.cal.ymd.members] as indicated:

    constexpr year_month_day& operator+=(const months& m) noexcept;
    

    -?- Constraints: If the argument supplied by the caller for the months parameter is convertible to years, its implicit conversion sequence to years is worse than its implicit conversion sequence to months ([over.ics.rank]).

    -7- Effects: *this = *this + m.

    […]

    constexpr year_month_day& operator-=(const months& m) noexcept;
    

    -?- Constraints: If the argument supplied by the caller for the months parameter is convertible to years, its implicit conversion sequence to years is worse than its implicit conversion sequence to months ([over.ics.rank]).

    -9- Effects: *this = *this - m.

    […]

  4. Modify [time.cal.ymd.nonmembers] as indicated:

    constexpr year_month_day operator+(const year_month_day& ymd, const months& dm) noexcept;
    

    -?- Constraints: If the argument supplied by the caller for the months parameter is convertible to years, its implicit conversion sequence to years is worse than its implicit conversion sequence to months ([over.ics.rank]).

    -3- Returns: (ymd.year() / ymd.month() + dm) / ymd.day().

    […]

    constexpr year_month_day operator+(const months& dm, const year_month_day& ymd) noexcept;
    

    -?- Constraints: If the argument supplied by the caller for the months parameter is convertible to years, its implicit conversion sequence to years is worse than its implicit conversion sequence to months ([over.ics.rank]).

    -5- Returns: ymd + dm.

    constexpr year_month_day operator+(const months& dm, const year_month_day& ymd) noexcept;
    

    -?- Constraints: If the argument supplied by the caller for the months parameter is convertible to years, its implicit conversion sequence to years is worse than its implicit conversion sequence to months ([over.ics.rank]).

    -6- Returns: ymd + (-dm).

  5. Modify [time.cal.ymdlast.members] as indicated:

    constexpr year_month_day_last& operator+=(const months& m) noexcept;
    

    -?- Constraints: If the argument supplied by the caller for the months parameter is convertible to years, its implicit conversion sequence to years is worse than its implicit conversion sequence to months ([over.ics.rank]).

    -2- Effects: *this = *this + m.

    […]

    constexpr year_month_day_last& operator-=(const months& m) noexcept;
    

    -?- Constraints: If the argument supplied by the caller for the months parameter is convertible to years, its implicit conversion sequence to years is worse than its implicit conversion sequence to months ([over.ics.rank]).

    -4- Effects: *this = *this - m.

    […]

  6. Modify [time.cal.ymdlast.nonmembers] as indicated:

    constexpr year_month_day_last
      operator+(const year_month_day_last& ymdl, const months& dm) noexcept;
    

    -?- Constraints: If the argument supplied by the caller for the months parameter is convertible to years, its implicit conversion sequence to years is worse than its implicit conversion sequence to months ([over.ics.rank]).

    -3- Returns: (ymdl.year() / ymdl.month() + dm) / last.

    constexpr year_month_day_last
      operator+(const months& dm, const year_month_day_last& ymdl) noexcept;
    

    -?- Constraints: If the argument supplied by the caller for the months parameter is convertible to years, its implicit conversion sequence to years is worse than its implicit conversion sequence to months ([over.ics.rank]).

    -4- Returns: ymdl + dm.

    constexpr year_month_day_last
      operator-(const year_month_day_last& ymdl, const months& dm) noexcept;
    

    -?- Constraints: If the argument supplied by the caller for the months parameter is convertible to years, its implicit conversion sequence to years is worse than its implicit conversion sequence to months ([over.ics.rank]).

    -5- Returns: ymdl + (-dm).

  7. Modify [time.cal.ymwd.members] as indicated:

    constexpr year_month_weekday& operator+=(const months& m) noexcept;
    

    -?- Constraints: If the argument supplied by the caller for the months parameter is convertible to years, its implicit conversion sequence to years is worse than its implicit conversion sequence to months ([over.ics.rank]).

    -6- Effects: *this = *this + m.

    […]

    constexpr year_month_weekday& operator-=(const months& m) noexcept;
    

    -?- Constraints: If the argument supplied by the caller for the months parameter is convertible to years, its implicit conversion sequence to years is worse than its implicit conversion sequence to months ([over.ics.rank]).

    -8- Effects: *this = *this - m.

    […]

  8. Modify [time.cal.ymwd.nonmembers] as indicated:

    constexpr year_month_weekday operator+(const year_month_weekday& ymwd, const months& dm) noexcept;
    

    -?- Constraints: If the argument supplied by the caller for the months parameter is convertible to years, its implicit conversion sequence to years is worse than its implicit conversion sequence to months ([over.ics.rank]).

    -2- Returns: (ymwd.year() / ymwd.month() + dm) / ymwd.weekday_indexed().

    constexpr year_month_weekday operator+(const months& dm, const year_month_weekday& ymwd) noexcept;
    

    -?- Constraints: If the argument supplied by the caller for the months parameter is convertible to years, its implicit conversion sequence to years is worse than its implicit conversion sequence to months ([over.ics.rank]).

    -3- Returns: ymwd + dm.

    constexpr year_month_weekday operator-(const year_month_weekday& ymwd, const months& dm) noexcept;
    

    -?- Constraints: If the argument supplied by the caller for the months parameter is convertible to years, its implicit conversion sequence to years is worse than its implicit conversion sequence to months ([over.ics.rank]).

    -4- Returns: ymwd + (-dm).

  9. Modify [time.cal.ymwdlast.members] as indicated:

    constexpr year_month_weekday_last& operator+=(const months& m) noexcept;
    

    -?- Constraints: If the argument supplied by the caller for the months parameter is convertible to years, its implicit conversion sequence to years is worse than its implicit conversion sequence to months ([over.ics.rank]).

    -2- Effects: *this = *this + m.

    […]

    constexpr year_month_weekday_last& operator-=(const months& m) noexcept;
    

    -?- Constraints: If the argument supplied by the caller for the months parameter is convertible to years, its implicit conversion sequence to years is worse than its implicit conversion sequence to months ([over.ics.rank]).

    -4- Effects: *this = *this - m.

    […]

  10. Modify [time.cal.ymwdlast.nonmembers] as indicated:

    constexpr year_month_weekday_last
      operator+(const year_month_weekday_last& ymwdl, const months& dm) noexcept;
    

    -?- Constraints: If the argument supplied by the caller for the months parameter is convertible to years, its implicit conversion sequence to years is worse than its implicit conversion sequence to months ([over.ics.rank]).

    -2- Returns: (ymwdl.year() / ymwdl.month() + dm) / ymwdl.weekday_last().

    constexpr year_month_weekday_last
      operator+(const months& dm, const year_month_weekday_last& ymwdl) noexcept;
    

    -?- Constraints: If the argument supplied by the caller for the months parameter is convertible to years, its implicit conversion sequence to years is worse than its implicit conversion sequence to months ([over.ics.rank]).

    -3- Returns: ymwdl + dm.

    constexpr year_month_weekday_last
      operator-(const year_month_weekday_last& ymwdl, const months& dm) noexcept;
    

    -?- Constraints: If the argument supplied by the caller for the months parameter is convertible to years, its implicit conversion sequence to years is worse than its implicit conversion sequence to months ([over.ics.rank]).

    -4- Returns: ymwdl + (-dm).

Date: 2020-02-14.11:24:43

[ 2020-02 Status to Immediate on Friday morning in Prague. ]

Date: 2020-02-15.00:00:00

[ 2020-02-13, Prague ]

Tim Song found a wording problem that we would like to resolve:

Given a class like

struct C : months {
  operator years();
};

The previous wording requires calls with a C argument to use the years overload, which would require implementation heroics since its conversion sequence to months is better than years.

Date: 2019-09-15.00:00:00

[ 2019-09-14; Tomasz and Howard provide concrete wording ]

Previous resolution [SUPERSEDED]:

This wording is relative to N4830.

[Drafting note: Suggested wording below assumes that we can add a Constraints: to a signature where the constraint does not apply to a deduced template. We have examples of such constraints in other parts of the WD (e.g. [unique.ptr.single.ctor]/p15, [unique.ptr.single.asgn]/p1). And we have the old form "does not participate …" being used for non-deduced templates in several places as well (e.g. [pairs.pair]/p5).

There are several ways of implementing such a constraint, such as adding a gratuitous template parameter.]

  1. Modify [time.cal.ym.members] as indicated:

    constexpr year_month& operator+=(const months& dm) noexcept;
    

    -?- Constraints: The argument supplied by the caller for the months parameter is not implicitly convertible to years.

    -4- Effects: *this = *this + dm.

    […]

    constexpr year_month& operator-=(const months& dm) noexcept;
    

    -?- Constraints: The argument supplied by the caller for the months parameter is not implicitly convertible to years.

    -6- Effects: *this = *this - dm.

    […]

  2. Modify [time.cal.ym.nonmembers] as indicated:

    constexpr year_month operator+(const year_month& ym, const months& dm) noexcept;
    

    -?- Constraints: The argument supplied by the caller for the months parameter is not implicitly convertible to years.

    -3- Returns: A year_month value z such that z - ym == dm.

    […]

    constexpr year_month operator+(const months& dm, const year_month& ym) noexcept;
    

    -?- Constraints: The argument supplied by the caller for the months parameter is not implicitly convertible to years.

    -5- Returns: ym + dm.

    constexpr year_month operator-(const year_month& ym, const months& dm) noexcept;
    

    -?- Constraints: The argument supplied by the caller for the months parameter is not implicitly convertible to years.

    -6- Returns: ym + -dm.

  3. Modify [time.cal.ymd.members] as indicated:

    constexpr year_month_day& operator+=(const months& m) noexcept;
    

    -?- Constraints: The argument supplied by the caller for the months parameter is not implicitly convertible to years.

    -7- Effects: *this = *this + m.

    […]

    constexpr year_month_day& operator-=(const months& m) noexcept;
    

    -?- Constraints: The argument supplied by the caller for the months parameter is not implicitly convertible to years.

    -9- Effects: *this = *this - m.

    […]

  4. Modify [time.cal.ymd.nonmembers] as indicated:

    constexpr year_month_day operator+(const year_month_day& ymd, const months& dm) noexcept;
    

    -?- Constraints: The argument supplied by the caller for the months parameter is not implicitly convertible to years.

    -3- Returns: (ymd.year() / ymd.month() + dm) / ymd.day().

    […]

    constexpr year_month_day operator+(const months& dm, const year_month_day& ymd) noexcept;
    

    -?- Constraints: The argument supplied by the caller for the months parameter is not implicitly convertible to years.

    -5- Returns: ymd + dm.

    constexpr year_month_day operator+(const months& dm, const year_month_day& ymd) noexcept;
    

    -?- Constraints: The argument supplied by the caller for the months parameter is not implicitly convertible to years.

    -6- Returns: ymd + (-dm).

  5. Modify [time.cal.ymdlast.members] as indicated:

    constexpr year_month_day_last& operator+=(const months& m) noexcept;
    

    -?- Constraints: The argument supplied by the caller for the months parameter is not implicitly convertible to years.

    -2- Effects: *this = *this + m.

    […]

    constexpr year_month_day_last& operator-=(const months& m) noexcept;
    

    -?- Constraints: The argument supplied by the caller for the months parameter is not implicitly convertible to years.

    -4- Effects: *this = *this - m.

    […]

  6. Modify [time.cal.ymdlast.nonmembers] as indicated:

    constexpr year_month_day_last
      operator+(const year_month_day_last& ymdl, const months& dm) noexcept;
    

    -?- Constraints: The argument supplied by the caller for the months parameter is not implicitly convertible to years.

    -3- Returns: (ymdl.year() / ymdl.month() + dm) / last.

    constexpr year_month_day_last
      operator+(const months& dm, const year_month_day_last& ymdl) noexcept;
    

    -?- Constraints: The argument supplied by the caller for the months parameter is not implicitly convertible to years.

    -4- Returns: ymdl + dm.

    constexpr year_month_day_last
      operator-(const year_month_day_last& ymdl, const months& dm) noexcept;
    

    -?- Constraints: The argument supplied by the caller for the months parameter is not implicitly convertible to years.

    -5- Returns: ymdl + (-dm).

  7. Modify [time.cal.ymwd.members] as indicated:

    constexpr year_month_weekday& operator+=(const months& m) noexcept;
    

    -?- Constraints: The argument supplied by the caller for the months parameter is not implicitly convertible to years.

    -6- Effects: *this = *this + m.

    […]

    constexpr year_month_weekday& operator-=(const months& m) noexcept;
    

    -?- Constraints: The argument supplied by the caller for the months parameter is not implicitly convertible to years.

    -8- Effects: *this = *this - m.

    […]

  8. Modify [time.cal.ymwd.nonmembers] as indicated:

    constexpr year_month_weekday operator+(const year_month_weekday& ymwd, const months& dm) noexcept;
    

    -?- Constraints: The argument supplied by the caller for the months parameter is not implicitly convertible to years.

    -2- Returns: (ymwd.year() / ymwd.month() + dm) / ymwd.weekday_indexed().

    constexpr year_month_weekday operator+(const months& dm, const year_month_weekday& ymwd) noexcept;
    

    -?- Constraints: The argument supplied by the caller for the months parameter is not implicitly convertible to years.

    -3- Returns: ymwd + dm.

    constexpr year_month_weekday operator-(const year_month_weekday& ymwd, const months& dm) noexcept;
    

    -?- Constraints: The argument supplied by the caller for the months parameter is not implicitly convertible to years.

    -4- Returns: ymwd + (-dm).

  9. Modify [time.cal.ymwdlast.members] as indicated:

    constexpr year_month_weekday_last& operator+=(const months& m) noexcept;
    

    -?- Constraints: The argument supplied by the caller for the months parameter is not implicitly convertible to years.

    -2- Effects: *this = *this + m.

    […]

    constexpr year_month_weekday_last& operator-=(const months& m) noexcept;
    

    -?- Constraints: The argument supplied by the caller for the months parameter is not implicitly convertible to years.

    -4- Effects: *this = *this - m.

    […]

  10. Modify [time.cal.ymwdlast.nonmembers] as indicated:

    constexpr year_month_weekday_last
      operator+(const year_month_weekday_last& ymwdl, const months& dm) noexcept;
    

    -?- Constraints: The argument supplied by the caller for the months parameter is not implicitly convertible to years.

    -2- Returns: (ymwdl.year() / ymwdl.month() + dm) / ymwdl.weekday_last().

    constexpr year_month_weekday_last
      operator+(const months& dm, const year_month_weekday_last& ymwdl) noexcept;
    

    -?- Constraints: The argument supplied by the caller for the months parameter is not implicitly convertible to years.

    -3- Returns: ymwdl + dm.

    constexpr year_month_weekday_last
      operator-(const year_month_weekday_last& ymwdl, const months& dm) noexcept;
    

    -?- Constraints: The argument supplied by the caller for the months parameter is not implicitly convertible to years.

    -4- Returns: ymwdl + (-dm).

Date: 2019-09-14.00:00:00

[ 2019-09-14 Priority set to 2 based on reflector discussion ]

Date: 2019-08-15.00:00:00

Currently, the year_month* types (year_month, year_month_day) provide separate arithmetic operators with duration type of years or months. This is an intentional optimization that avoids performing modulo arithmetic in the former case.

However, these make the arithmetic of year_month* types with durations that are convertible to years (and by consequence to months) ambiguous. For example, the following code is ambiguous:

using decades = duration<int, ratio_multiply<ratio<10>, years::period>>;
auto ymd = 2001y/January/1d;
ymd += decades(1); // error, ambiguous

while less usual durations that are only convertible to months work correctly:

using decamonths = duration<int, ratio_multiply<ratio<10>, months::period>>;
auto ymd = 2001y/January/1d;
ymd += decamonths(1);

The example implementation resolves the issues by making sure that the years overload will be preferred in case of ambiguity, by declaring the months overload a function template with a default argument for its parameter (suggested by Tim Song):

template<class = unspecified>
constexpr year_month_weekday& operator+=(const months& m) noexcept;
constexpr year_month_weekday& operator+=(const years& m) noexcept;
History
Date User Action Args
2021-02-25 10:48:01adminsetstatus: wp -> c++20
2020-02-24 16:02:59adminsetstatus: immediate -> wp
2020-02-14 11:24:43adminsetstatus: new -> immediate
2020-02-14 11:24:43adminsetmessages: + msg11118
2020-02-14 11:24:43adminsetstatus: new -> new
2020-02-14 10:10:27adminsetstatus: immediate -> new
2020-02-13 22:05:05adminsetmessages: + msg11089
2019-09-15 11:30:37adminsetmessages: + msg10619
2019-09-15 11:30:37adminsetmessages: + msg10618
2019-09-14 12:41:09adminsetmessages: + msg10610
2019-08-15 00:00:00admincreate