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: 27.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.11.1.3.2 [unique.ptr.single.ctor]/p15, 20.11.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. 20.4.2 [pairs.pair]/p5).

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

> 1. Modify 27.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 27.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 27.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 27.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 27.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 27.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 27.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 27.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 27.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 27.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.11.1.3.2 [unique.ptr.single.ctor]/p15, 20.11.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. 20.4.2 [pairs.pair]/p5).

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

1. Modify 27.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 27.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 27.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 27.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 27.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 27.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 27.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 27.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 27.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 27.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)`.