libstdc++: Fix operator overload ambiguity with calendar types

We currently don't enforce a constraint on some of the calendar types'
addition/subtraction operator overloads that take a 'months' arguments:

  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.

This constraint is relevant when adding/subtracting a duration to/from,
say, a year_month where the given duration is convertible to both
'months' and to 'years' (as in the new testcases below).  The correct
behavior here in light of this constraint is to perform the operation
through the (more efficient) 'years'-based overload, but we currently
emit an ambiguous overload error.

This patch templatizes the 'months'-based addition/subtraction operator
overloads so that in the event of an implicit-conversion tie, we select
the non-template 'years'-based overload.  This is the same approach
that the date library takes for enforcing this constraint.

libstdc++-v3/ChangeLog:

	* include/std/chrono
	(__detail::__months_years_conversion_disambiguator): Define.
	(year_month::operator+=): Templatize the 'months'-based overload
	so that the 'years'-based overload is selected in case of
	equally-ranked implicit conversion sequences to both 'months'
	and 'years' from the supplied argument.
	(year_month::operator-=): Likewise.
	(year_month::operator+): Likewise.
	(year_month::operator-): Likewise.
	(year_month_day::operator+=): Likewise.
	(year_month_day::operator-=): Likewise.
	(year_month_day::operator+): Likewise.
	(year_month_day::operator-): Likewise.
	(year_month_day_last::operator+=): Likewise.
	(year_month_day_last::operator-=): Likewise.
	(year_month_day_last::operator+): Likewise
	(year_month_day_last::operator-): Likewise.
	(year_month_day_weekday::operator+=): Likewise
	(year_month_day_weekday::operator-=): Likewise.
	(year_month_day_weekday::operator+): Likewise.
	(year_month_day_weekday::operator-): Likewise.
	(year_month_day_weekday_last::operator+=): Likewise
	(year_month_day_weekday_last::operator-=): Likewise.
	(year_month_day_weekday_last::operator+): Likewise.
	(year_month_day_weekday_last::operator-): Likewise.
	(testsuite/std/time/year_month/2.cc): New test.
	(testsuite/std/time/year_month_day/2.cc): New test.
	(testsuite/std/time/year_month_day_last/2.cc): New test.
	(testsuite/std/time/year_month_weekday/2.cc): New test.
	(testsuite/std/time/year_month_weekday_last/2.cc): New test.
This commit is contained in:
Patrick Palka 2020-08-27 14:09:52 -04:00
parent 04df5e7de2
commit 7b743c67f0
6 changed files with 367 additions and 119 deletions

View File

@ -2046,6 +2046,23 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
// YEAR_MONTH
namespace __detail
{
// [time.cal.ym], [time.cal.ymd], etc constrain the 'months'-based
// addition/subtraction operator overloads like so:
//
// 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.
//
// We realize this constraint by templatizing the 'months'-based
// overloads (using a dummy defaulted template parameter), so that
// overload resolution doesn't select the 'months'-based overload unless
// the implicit conversion sequence to 'months' is better than that to
// 'years'.
using __months_years_conversion_disambiguator = void;
}
class year_month
{
private:
@ -2068,19 +2085,21 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
month() const noexcept
{ return _M_m; }
constexpr year_month&
operator+=(const months& __dm) noexcept
{
*this = *this + __dm;
return *this;
}
template<typename = __detail::__months_years_conversion_disambiguator>
constexpr year_month&
operator+=(const months& __dm) noexcept
{
*this = *this + __dm;
return *this;
}
constexpr year_month&
operator-=(const months& __dm) noexcept
{
*this = *this - __dm;
return *this;
}
template<typename = __detail::__months_years_conversion_disambiguator>
constexpr year_month&
operator-=(const months& __dm) noexcept
{
*this = *this - __dm;
return *this;
}
constexpr year_month&
operator+=(const years& __dy) noexcept
@ -2108,25 +2127,28 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
operator<=>(const year_month& __x, const year_month& __y) noexcept
= default;
friend constexpr year_month
operator+(const year_month& __ym, const months& __dm) noexcept
{
// TODO: Optimize?
auto __m = __ym.month() + __dm;
auto __i = unsigned{__ym.month()} - 1 + __dm.count();
auto __y = (__i < 0
? __ym.year() + years{(__i - 11) / 12}
: __ym.year() + years{__i / 12});
return __y / __m;
}
template<typename = __detail::__months_years_conversion_disambiguator>
friend constexpr year_month
operator+(const year_month& __ym, const months& __dm) noexcept
{
// TODO: Optimize?
auto __m = __ym.month() + __dm;
auto __i = unsigned{__ym.month()} - 1 + __dm.count();
auto __y = (__i < 0
? __ym.year() + years{(__i - 11) / 12}
: __ym.year() + years{__i / 12});
return __y / __m;
}
friend constexpr year_month
operator+(const months& __dm, const year_month& __ym) noexcept
{ return __ym + __dm; }
template<typename = __detail::__months_years_conversion_disambiguator>
friend constexpr year_month
operator+(const months& __dm, const year_month& __ym) noexcept
{ return __ym + __dm; }
friend constexpr year_month
operator-(const year_month& __ym, const months& __dm) noexcept
{ return __ym + -__dm; }
template<typename = __detail::__months_years_conversion_disambiguator>
friend constexpr year_month
operator-(const year_month& __ym, const months& __dm) noexcept
{ return __ym + -__dm; }
friend constexpr months
operator-(const year_month& __x, const year_month& __y) noexcept
@ -2200,19 +2222,21 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
: year_month_day(sys_days{__dp.time_since_epoch()})
{ }
constexpr year_month_day&
operator+=(const months& __m) noexcept
{
*this = *this + __m;
return *this;
}
template<typename = __detail::__months_years_conversion_disambiguator>
constexpr year_month_day&
operator+=(const months& __m) noexcept
{
*this = *this + __m;
return *this;
}
constexpr year_month_day&
operator-=(const months& __m) noexcept
{
*this = *this - __m;
return *this;
}
template<typename = __detail::__months_years_conversion_disambiguator>
constexpr year_month_day&
operator-=(const months& __m) noexcept
{
*this = *this - __m;
return *this;
}
constexpr year_month_day&
operator+=(const years& __y) noexcept
@ -2262,13 +2286,15 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
operator<=>(const year_month_day& __x, const year_month_day& __y) noexcept
= default;
friend constexpr year_month_day
operator+(const year_month_day& __ymd, const months& __dm) noexcept
{ return (__ymd.year() / __ymd.month() + __dm) / __ymd.day(); }
template<typename = __detail::__months_years_conversion_disambiguator>
friend constexpr year_month_day
operator+(const year_month_day& __ymd, const months& __dm) noexcept
{ return (__ymd.year() / __ymd.month() + __dm) / __ymd.day(); }
friend constexpr year_month_day
operator+(const months& __dm, const year_month_day& __ymd) noexcept
{ return __ymd + __dm; }
template<typename = __detail::__months_years_conversion_disambiguator>
friend constexpr year_month_day
operator+(const months& __dm, const year_month_day& __ymd) noexcept
{ return __ymd + __dm; }
friend constexpr year_month_day
operator+(const year_month_day& __ymd, const years& __dy) noexcept
@ -2278,9 +2304,10 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
operator+(const years& __dy, const year_month_day& __ymd) noexcept
{ return __ymd + __dy; }
friend constexpr year_month_day
operator-(const year_month_day& __ymd, const months& __dm) noexcept
{ return __ymd + -__dm; }
template<typename = __detail::__months_years_conversion_disambiguator>
friend constexpr year_month_day
operator-(const year_month_day& __ymd, const months& __dm) noexcept
{ return __ymd + -__dm; }
friend constexpr year_month_day
operator-(const year_month_day& __ymd, const years& __dy) noexcept
@ -2364,19 +2391,21 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
: _M_y{__y}, _M_mdl{__mdl}
{ }
constexpr year_month_day_last&
operator+=(const months& __m) noexcept
{
*this = *this + __m;
return *this;
}
template<typename = __detail::__months_years_conversion_disambiguator>
constexpr year_month_day_last&
operator+=(const months& __m) noexcept
{
*this = *this + __m;
return *this;
}
constexpr year_month_day_last&
operator-=(const months& __m) noexcept
{
*this = *this - __m;
return *this;
}
template<typename = __detail::__months_years_conversion_disambiguator>
constexpr year_month_day_last&
operator-=(const months& __m) noexcept
{
*this = *this - __m;
return *this;
}
constexpr year_month_day_last&
operator+=(const years& __y) noexcept
@ -2438,20 +2467,23 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
const year_month_day_last& __y) noexcept
= default;
friend constexpr year_month_day_last
operator+(const year_month_day_last& __ymdl,
const months& __dm) noexcept
{ return (__ymdl.year() / __ymdl.month() + __dm) / last; }
template<typename = __detail::__months_years_conversion_disambiguator>
friend constexpr year_month_day_last
operator+(const year_month_day_last& __ymdl,
const months& __dm) noexcept
{ return (__ymdl.year() / __ymdl.month() + __dm) / last; }
friend constexpr year_month_day_last
operator+(const months& __dm,
const year_month_day_last& __ymdl) noexcept
{ return __ymdl + __dm; }
template<typename = __detail::__months_years_conversion_disambiguator>
friend constexpr year_month_day_last
operator+(const months& __dm,
const year_month_day_last& __ymdl) noexcept
{ return __ymdl + __dm; }
friend constexpr year_month_day_last
operator-(const year_month_day_last& __ymdl,
const months& __dm) noexcept
{ return __ymdl + -__dm; }
template<typename = __detail::__months_years_conversion_disambiguator>
friend constexpr year_month_day_last
operator-(const year_month_day_last& __ymdl,
const months& __dm) noexcept
{ return __ymdl + -__dm; }
friend constexpr year_month_day_last
operator+(const year_month_day_last& __ymdl,
@ -2544,19 +2576,21 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
: year_month_weekday{sys_days{__dp.time_since_epoch()}}
{ }
constexpr year_month_weekday&
operator+=(const months& __m) noexcept
{
*this = *this + __m;
return *this;
}
template<typename = __detail::__months_years_conversion_disambiguator>
constexpr year_month_weekday&
operator+=(const months& __m) noexcept
{
*this = *this + __m;
return *this;
}
constexpr year_month_weekday&
operator-=(const months& __m) noexcept
{
*this = *this - __m;
return *this;
}
template<typename = __detail::__months_years_conversion_disambiguator>
constexpr year_month_weekday&
operator-=(const months& __m) noexcept
{
*this = *this - __m;
return *this;
}
constexpr year_month_weekday&
operator+=(const years& __y) noexcept
@ -2626,13 +2660,18 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
&& __x.weekday_indexed() == __y.weekday_indexed();
}
friend constexpr year_month_weekday
operator+(const year_month_weekday& __ymwd, const months& __dm) noexcept
{ return (__ymwd.year() / __ymwd.month() + __dm) / __ymwd.weekday_indexed(); }
template<typename = __detail::__months_years_conversion_disambiguator>
friend constexpr year_month_weekday
operator+(const year_month_weekday& __ymwd, const months& __dm) noexcept
{
return ((__ymwd.year() / __ymwd.month() + __dm)
/ __ymwd.weekday_indexed());
}
friend constexpr year_month_weekday
operator+(const months& __dm, const year_month_weekday& __ymwd) noexcept
{ return __ymwd + __dm; }
template<typename = __detail::__months_years_conversion_disambiguator>
friend constexpr year_month_weekday
operator+(const months& __dm, const year_month_weekday& __ymwd) noexcept
{ return __ymwd + __dm; }
friend constexpr year_month_weekday
operator+(const year_month_weekday& __ymwd, const years& __dy) noexcept
@ -2642,9 +2681,10 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
operator+(const years& __dy, const year_month_weekday& __ymwd) noexcept
{ return __ymwd + __dy; }
friend constexpr year_month_weekday
operator-(const year_month_weekday& __ymwd, const months& __dm) noexcept
{ return __ymwd + -__dm; }
template<typename = __detail::__months_years_conversion_disambiguator>
friend constexpr year_month_weekday
operator-(const year_month_weekday& __ymwd, const months& __dm) noexcept
{ return __ymwd + -__dm; }
friend constexpr year_month_weekday
operator-(const year_month_weekday& __ymwd, const years& __dy) noexcept
@ -2690,19 +2730,21 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
: _M_y{__y}, _M_m{__m}, _M_wdl{__wdl}
{ }
constexpr year_month_weekday_last&
operator+=(const months& __m) noexcept
{
*this = *this + __m;
return *this;
}
template<typename = __detail::__months_years_conversion_disambiguator>
constexpr year_month_weekday_last&
operator+=(const months& __m) noexcept
{
*this = *this + __m;
return *this;
}
constexpr year_month_weekday_last&
operator-=(const months& __m) noexcept
{
*this = *this - __m;
return *this;
}
template<typename = __detail::__months_years_conversion_disambiguator>
constexpr year_month_weekday_last&
operator-=(const months& __m) noexcept
{
*this = *this - __m;
return *this;
}
constexpr year_month_weekday_last&
operator+=(const years& __y) noexcept
@ -2759,15 +2801,20 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
&& __x.weekday_last() == __y.weekday_last();
}
friend constexpr year_month_weekday_last
operator+(const year_month_weekday_last& __ymwdl,
const months& __dm) noexcept
{ return (__ymwdl.year() / __ymwdl.month() + __dm) / __ymwdl.weekday_last(); }
template<typename = __detail::__months_years_conversion_disambiguator>
friend constexpr year_month_weekday_last
operator+(const year_month_weekday_last& __ymwdl,
const months& __dm) noexcept
{
return ((__ymwdl.year() / __ymwdl.month() + __dm)
/ __ymwdl.weekday_last());
}
friend constexpr year_month_weekday_last
operator+(const months& __dm,
const year_month_weekday_last& __ymwdl) noexcept
{ return __ymwdl + __dm; }
template<typename = __detail::__months_years_conversion_disambiguator>
friend constexpr year_month_weekday_last
operator+(const months& __dm,
const year_month_weekday_last& __ymwdl) noexcept
{ return __ymwdl + __dm; }
friend constexpr year_month_weekday_last
operator+(const year_month_weekday_last& __ymwdl,
@ -2779,10 +2826,11 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
const year_month_weekday_last& __ymwdl) noexcept
{ return __ymwdl + __dy; }
friend constexpr year_month_weekday_last
operator-(const year_month_weekday_last& __ymwdl,
const months& __dm) noexcept
{ return __ymwdl + -__dm; }
template<typename = __detail::__months_years_conversion_disambiguator>
friend constexpr year_month_weekday_last
operator-(const year_month_weekday_last& __ymwdl,
const months& __dm) noexcept
{ return __ymwdl + -__dm; }
friend constexpr year_month_weekday_last
operator-(const year_month_weekday_last& __ymwdl,

View File

@ -0,0 +1,40 @@
// { dg-options "-std=gnu++2a" }
// { dg-do compile { target c++2a } }
// Copyright (C) 2020 Free Software Foundation, Inc.
//
// This file is part of the GNU ISO C++ Library. This library is free
// software; you can redistribute it and/or modify it under the
// terms of the GNU General Public License as published by the
// Free Software Foundation; either version 3, or (at your option)
// any later version.
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License along
// with this library; see the file COPYING3. If not see
// <http://www.gnu.org/licenses/>.
// Class template year_month [time.cal.year_month]
#include <chrono>
constexpr void
constexpr_year_month_op_overload_disambiguation()
{
using namespace std::chrono;
using decades = duration<long long, std::ratio<31556952 * 10>>;
static_assert(std::convertible_to<decades, months>
&& std::convertible_to<decades, years>);
using ym = year_month;
constexpr ym ym1 = 2015y/June;
static_assert(ym1 + decades{1} == 2025y/June);
static_assert(ym1 - decades{1} == 2005y/June);
static_assert(decades{1} + ym1 == 2025y/June);
static_assert((ym{ym1} += decades{1}) == 2025y/June);
static_assert((ym{ym1} -= decades{1}) == 2005y/June);
}

View File

@ -0,0 +1,40 @@
// { dg-options "-std=gnu++2a" }
// { dg-do compile { target c++2a } }
// Copyright (C) 2020 Free Software Foundation, Inc.
//
// This file is part of the GNU ISO C++ Library. This library is free
// software; you can redistribute it and/or modify it under the
// terms of the GNU General Public License as published by the
// Free Software Foundation; either version 3, or (at your option)
// any later version.
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License along
// with this library; see the file COPYING3. If not see
// <http://www.gnu.org/licenses/>.
// Class template year_month_day [time.cal.year_month_day]
#include <chrono>
constexpr void
constexpr_year_month_day_op_overload_disambiguation()
{
using namespace std::chrono;
using decades = duration<long long, std::ratio<31556952 * 10>>;
static_assert(std::convertible_to<decades, months>
&& std::convertible_to<decades, years>);
using ymd = year_month_day;
constexpr ymd ymd1 = 2015y/June/15d;
static_assert(ymd1 + decades{1} == 2025y/June/15d);
static_assert(ymd1 - decades{1} == 2005y/June/15d);
static_assert(decades{1} + ymd1 == 2025y/June/15d);
static_assert((ymd{ymd1} += decades{1}) == 2025y/June/15d);
static_assert((ymd{ymd1} -= decades{1}) == 2005y/June/15d);
}

View File

@ -0,0 +1,40 @@
// { dg-options "-std=gnu++2a" }
// { dg-do compile { target c++2a } }
// Copyright (C) 2020 Free Software Foundation, Inc.
//
// This file is part of the GNU ISO C++ Library. This library is free
// software; you can redistribute it and/or modify it under the
// terms of the GNU General Public License as published by the
// Free Software Foundation; either version 3, or (at your option)
// any later version.
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License along
// with this library; see the file COPYING3. If not see
// <http://www.gnu.org/licenses/>.
// Class template year_month_day_last [time.cal.year_month_day_last]
#include <chrono>
constexpr void
constexpr_year_month_day_last_op_overload_disambiguation()
{
using namespace std::chrono;
using decades = duration<long long, std::ratio<31556952 * 10>>;
static_assert(std::convertible_to<decades, months>
&& std::convertible_to<decades, years>);
using ymdl = year_month_day_last;
constexpr ymdl ymdl1 = 2015y/June/last;
static_assert(ymdl1 + decades{1} == 2025y/June/last);
static_assert(ymdl1 - decades{1} == 2005y/June/last);
static_assert(decades{1} + ymdl1 == 2025y/June/last);
static_assert((ymdl{ymdl1} += decades{1}) == 2025y/June/last);
static_assert((ymdl{ymdl1} -= decades{1}) == 2005y/June/last);
}

View File

@ -0,0 +1,40 @@
// { dg-options "-std=gnu++2a" }
// { dg-do compile { target c++2a } }
// Copyright (C) 2020 Free Software Foundation, Inc.
//
// This file is part of the GNU ISO C++ Library. This library is free
// software; you can redistribute it and/or modify it under the
// terms of the GNU General Public License as published by the
// Free Software Foundation; either version 3, or (at your option)
// any later version.
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License along
// with this library; see the file COPYING3. If not see
// <http://www.gnu.org/licenses/>.
// Class template year_month_weekday [time.cal.year_month_weekday]
#include <chrono>
constexpr void
constexpr_year_month_weekday_op_overload_disambiguation()
{
using namespace std::chrono;
using decades = duration<long long, std::ratio<31556952 * 10>>;
static_assert(std::convertible_to<decades, months>
&& std::convertible_to<decades, years>);
using ymwd = year_month_weekday;
constexpr ymwd ymwd1 = 2015y/June/Monday[3];
static_assert(ymwd1 + decades{1} == 2025y/June/Monday[3]);
static_assert(ymwd1 - decades{1} == 2005y/June/Monday[3]);
static_assert(decades{1} + ymwd1 == 2025y/June/Monday[3]);
static_assert((ymwd{ymwd1} += decades{1}) == 2025y/June/Monday[3]);
static_assert((ymwd{ymwd1} -= decades{1}) == 2005y/June/Monday[3]);
}

View File

@ -0,0 +1,40 @@
// { dg-options "-std=gnu++2a" }
// { dg-do compile { target c++2a } }
// Copyright (C) 2020 Free Software Foundation, Inc.
//
// This file is part of the GNU ISO C++ Library. This library is free
// software; you can redistribute it and/or modify it under the
// terms of the GNU General Public License as published by the
// Free Software Foundation; either version 3, or (at your option)
// any later version.
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License along
// with this library; see the file COPYING3. If not see
// <http://www.gnu.org/licenses/>.
// Class template year_month_weekday_last [time.cal.year_month_weekday_last]
#include <chrono>
constexpr void
constexpr_year_month_weekday_last_op_overload_disambiguation()
{
using namespace std::chrono;
using decades = duration<long long, std::ratio<31556952 * 10>>;
static_assert(std::convertible_to<decades, months>
&& std::convertible_to<decades, years>);
using ymwdl = year_month_weekday_last;
constexpr ymwdl ymwdl1 = 2015y/June/Monday[last];
static_assert(ymwdl1 + decades{1} == 2025y/June/Monday[last]);
static_assert(ymwdl1 - decades{1} == 2005y/June/Monday[last]);
static_assert(decades{1} + ymwdl1 == 2025y/June/Monday[last]);
static_assert((ymwdl{ymwdl1} += decades{1}) == 2025y/June/Monday[last]);
static_assert((ymwdl{ymwdl1} -= decades{1}) == 2005y/June/Monday[last]);
}