This page is a snapshot from the LWG issues list, see the Library Active Issues List for more information and the meaning of C++20 status.

3260. year_month* arithmetic rejects durations convertible to years

Section: 29.8 [time.cal] Status: C++20 Submitter: Tomasz Kamiński Opened: 2019-08-15 Last modified: 2021-02-25

Priority: 2

View all issues with C++20 status.

Discussion:

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;

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

[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. 20.3.1.3.2 [unique.ptr.single.ctor]/p15, 20.3.1.3.4 [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. 22.3.2 [pairs.pair]/p5).

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

  1. Modify 29.8.13.2 [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 29.8.13.3 [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 29.8.14.2 [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 29.8.14.3 [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 29.8.15.2 [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 29.8.15.3 [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 29.8.16.2 [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 29.8.16.3 [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 29.8.17.2 [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 29.8.17.3 [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).

[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.

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

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. 20.3.1.3.2 [unique.ptr.single.ctor]/p15, 20.3.1.3.4 [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. 22.3.2 [pairs.pair]/p5).

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

  1. Modify 29.8.13.2 [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 (12.2.4.3 [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 (12.2.4.3 [over.ics.rank]).

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

    […]

  2. Modify 29.8.13.3 [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 (12.2.4.3 [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 (12.2.4.3 [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 (12.2.4.3 [over.ics.rank]).

    -6- Returns: ym + -dm.

  3. Modify 29.8.14.2 [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 (12.2.4.3 [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 (12.2.4.3 [over.ics.rank]).

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

    […]

  4. Modify 29.8.14.3 [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 (12.2.4.3 [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 (12.2.4.3 [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 (12.2.4.3 [over.ics.rank]).

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

  5. Modify 29.8.15.2 [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 (12.2.4.3 [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 (12.2.4.3 [over.ics.rank]).

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

    […]

  6. Modify 29.8.15.3 [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 (12.2.4.3 [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 (12.2.4.3 [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 (12.2.4.3 [over.ics.rank]).

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

  7. Modify 29.8.16.2 [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 (12.2.4.3 [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 (12.2.4.3 [over.ics.rank]).

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

    […]

  8. Modify 29.8.16.3 [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 (12.2.4.3 [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 (12.2.4.3 [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 (12.2.4.3 [over.ics.rank]).

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

  9. Modify 29.8.17.2 [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 (12.2.4.3 [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 (12.2.4.3 [over.ics.rank]).

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

    […]

  10. Modify 29.8.17.3 [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 (12.2.4.3 [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 (12.2.4.3 [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 (12.2.4.3 [over.ics.rank]).

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