]> git.ipfire.org Git - thirdparty/gcc.git/commitdiff
libstdc++: Extend __is_standard_integer to cover extended integer types
authorJonathan Wakely <jwakely@redhat.com>
Fri, 5 Dec 2025 20:54:32 +0000 (20:54 +0000)
committerJonathan Wakely <redi@gcc.gnu.org>
Mon, 8 Dec 2025 18:55:02 +0000 (18:55 +0000)
We have __is_signed_integer and __is_unsigned_integer traits which
should have been updated by r16-2190-g4faa42ac0dee2c when making
__int128 an extended integer type (for PR libstdc++/96710). Currently
they check whether the type is a signed integer type or an unsigned
integer type, or a cv-qualified version of one of those. This doesn't
match the standard's definition, which does not include cv-qualified
types. This change ensures that signed __int128 and unsigned __int128
are included in those traits in strict -std modes, and it removes the
use of remove_cv_t so that they are not true for cv-qualified types.
This makes the traits match the meaning of "signed integer type" and
"unsigned integer type" in the standard ([basic.fundamental]).

We also have an __is_standard_integer trait, which is true if either
__is_signed_integer or __is_unsigned_integer is true, but that's also
not a match for the definition in the standard. The definitions of
"signed integer type" and "unsigned integer type" include both standard
and extended integer types, so only saying "standard" in the trait name
is misleading (even before this change, because in non-strict -std modes
the __GLIBCXX_TYPE_INT_N_0 .. __GLIBCXX_TYPE_INT_N_3 types were always
included in the trait, and they aren't standard integer types).

This change renames __is_standard_integer to the more accurate
__is_signed_or_unsigned_integer. Because the set of signed and
unsigned integer types is the same as the set of standard and extended
integer types, the trait could instead have been renamed to
__is_standard_or_extended_integer. I think it's clearer and more
self-explanatory to avoid "standard and extended" and name it for the
signed and unsigned integer types.

N.B. we don't want to call it just __is_integer_type because the integer
types includes cv-qualified types and also bool and the character types
char, wchar_t, char16_t etc.

The consequences of redefining and renaming these traits are small, and
only positive.

Apart from the uses in the __is_standard_integer trait, the only other
uses of __is_signed_integer and __is_unsigned_integer are in <format>
and those uses are unaffected by this change to add 128-bit integers to
the traits. In both uses the type argument is already cv-unqualified,
and there is already explicit handling for 128-bit integers where that
is required.

The existing uses of __is_standard_integer can simply be changed to use
the new name. This does change the behaviour of those uses of the trait,
because the __is_signed_or_unsigned_integer trait now includes
128-bit integers in strict modes. However, that is a desirable change
that fixes some bugs. Specifically, the [utility.intcmp] functions such
as std::cmp_less and the [numeric.sat.arith] functions such as
std::add_sat did not support 128-bit integers in strict modes. Since the
standard says they should be enabled for all signed and unsigned integer
types (or equivalently, for all standard and extended integer types),
those functions should all support __int128 and unsigned __int128. That
is fixed by this change.  Additionally, the same changes in <charconv>,
<mdspan>, and <stdckdint.h> enable the use of 128-bit integers for those
APIs in strict modes.

Finally, this also make a drive-by fix to the enable_if constraints for
the integer overloads of std::from_chars. That used remove_cv_t and so
enabled the overload for lvalue arguments of type const char, which
won't work and should not be enabled.

libstdc++-v3/ChangeLog:

* include/bits/intcmp.h: Replace all uses of
__is_standard_integer with __is_signed_or_unsigned_integer.
* include/bits/max_size_type.h: Fix outdated comment.
* include/bits/sat_arith.h: Replace all uses of
__is_standard_integer with __is_signed_or_unsigned_integer.
* include/c_compatibility/stdckdint.h: Replace all uses of the
__cv_unqual_signed_or_unsigned_integer_type concept with
__is_signed_or_unsigned_integer.
(__cv_unqual_signed_or_unsigned_integer_type): Remove.
* include/ext/numeric_traits.h: Fix outdated comment.
* include/std/charconv (from_chars): Replace use of
__is_standard_integer with __is_signed_or_unsigned_integer.
Do not enable for cv-qualified char.
* include/std/mdspan: Likewise.
* include/std/type_traits (__is_unsigned_integer): Include
unsigned __int128 in type list.
(__is_signed_integer): Include signed __int128 in type list.
(__is_standard_integer): Rename to ...
(__is_signed_or_unsigned_integer): ... this.
* testsuite/23_containers/mdspan/extents/ctor_ints.cc: Test
with 128-bit integers.
* testsuite/23_containers/mdspan/submdspan/strided_slice.cc:
Likewise.
* testsuite/20_util/integer_comparisons/extended.cc: New test.
* testsuite/26_numerics/saturation/extended.cc: New test.
* testsuite/26_numerics/stdckdint/extended.cc: New test.

Reviewed-by: Patrick Palka <ppalka@redhat.com>
13 files changed:
libstdc++-v3/include/bits/intcmp.h
libstdc++-v3/include/bits/max_size_type.h
libstdc++-v3/include/bits/sat_arith.h
libstdc++-v3/include/c_compatibility/stdckdint.h
libstdc++-v3/include/ext/numeric_traits.h
libstdc++-v3/include/std/charconv
libstdc++-v3/include/std/mdspan
libstdc++-v3/include/std/type_traits
libstdc++-v3/testsuite/20_util/integer_comparisons/extended.cc [new file with mode: 0644]
libstdc++-v3/testsuite/23_containers/mdspan/extents/ctor_ints.cc
libstdc++-v3/testsuite/23_containers/mdspan/submdspan/strided_slice.cc
libstdc++-v3/testsuite/26_numerics/saturation/extended.cc [new file with mode: 0644]
libstdc++-v3/testsuite/26_numerics/stdckdint/extended.cc [new file with mode: 0644]

index 3d3fbc43d2c80e2127ef5b744766b781a4224754..bb9c7f2c7ff60e78d0bd9ec3a6b6f1273a6a5680 100644 (file)
@@ -49,8 +49,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
     constexpr bool
     cmp_equal(_Tp __t, _Up __u) noexcept
     {
-      static_assert(__is_standard_integer<_Tp>::value);
-      static_assert(__is_standard_integer<_Up>::value);
+      static_assert(__is_signed_or_unsigned_integer<_Tp>::value);
+      static_assert(__is_signed_or_unsigned_integer<_Up>::value);
 
       if constexpr (is_signed_v<_Tp> == is_signed_v<_Up>)
        return __t == __u;
@@ -69,8 +69,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
     constexpr bool
     cmp_less(_Tp __t, _Up __u) noexcept
     {
-      static_assert(__is_standard_integer<_Tp>::value);
-      static_assert(__is_standard_integer<_Up>::value);
+      static_assert(__is_signed_or_unsigned_integer<_Tp>::value);
+      static_assert(__is_signed_or_unsigned_integer<_Up>::value);
 
       if constexpr (is_signed_v<_Tp> == is_signed_v<_Up>)
        return __t < __u;
@@ -99,8 +99,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
     constexpr bool
     in_range(_Tp __t) noexcept
     {
-      static_assert(__is_standard_integer<_Res>::value);
-      static_assert(__is_standard_integer<_Tp>::value);
+      static_assert(__is_signed_or_unsigned_integer<_Res>::value);
+      static_assert(__is_signed_or_unsigned_integer<_Tp>::value);
       using __gnu_cxx::__int_traits;
 
       if constexpr (is_signed_v<_Tp> == is_signed_v<_Res>)
index a34b91a04f1e6b82c78792c41fc1e9b73529c8b1..537aceeeca02b80fcf78a65f253295daa87b0d68 100644 (file)
 // [iterator.concept.winc]) that are one bit wider than the widest supported
 // integer type.
 //
-// The set of integer types we consider includes __int128 and unsigned __int128
-// (when they exist), even though they are really integer types only in GNU
-// mode.  This is to obtain a consistent ABI for these integer-class types
-// across strict mode and GNU mode.
+// The set of integer types we consider includes the extended integer types
+// __int128 and unsigned __int128 (when they exist).
 
 namespace std _GLIBCXX_VISIBILITY(default)
 {
index e036fc88e40bae3e2563b4c34fa72e7374ccd8d6..bce86d9199192c127ae800e27c3d2d50a85d0c23 100644 (file)
@@ -46,7 +46,7 @@ namespace std _GLIBCXX_VISIBILITY(default)
 _GLIBCXX_BEGIN_NAMESPACE_VERSION
 
   /// Add two integers, with saturation in case of overflow.
-  template<typename _Tp> requires __is_standard_integer<_Tp>::value
+  template<typename _Tp> requires __is_signed_or_unsigned_integer<_Tp>::value
     constexpr _Tp
     add_sat(_Tp __x, _Tp __y) noexcept
     {
@@ -62,7 +62,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
     }
 
   /// Subtract one integer from another, with saturation in case of overflow.
-  template<typename _Tp> requires __is_standard_integer<_Tp>::value
+  template<typename _Tp> requires __is_signed_or_unsigned_integer<_Tp>::value
     constexpr _Tp
     sub_sat(_Tp __x, _Tp __y) noexcept
     {
@@ -78,7 +78,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
     }
 
   /// Multiply two integers, with saturation in case of overflow.
-  template<typename _Tp> requires __is_standard_integer<_Tp>::value
+  template<typename _Tp> requires __is_signed_or_unsigned_integer<_Tp>::value
     constexpr _Tp
     mul_sat(_Tp __x, _Tp __y) noexcept
     {
@@ -94,7 +94,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
     }
 
   /// Divide one integer by another, with saturation in case of overflow.
-  template<typename _Tp> requires __is_standard_integer<_Tp>::value
+  template<typename _Tp> requires __is_signed_or_unsigned_integer<_Tp>::value
     constexpr _Tp
     div_sat(_Tp __x, _Tp __y) noexcept
     {
@@ -107,8 +107,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 
   /// Divide one integer by another, with saturation in case of overflow.
   template<typename _Res, typename _Tp>
-    requires __is_standard_integer<_Res>::value
-      && __is_standard_integer<_Tp>::value
+    requires __is_signed_or_unsigned_integer<_Res>::value
+      && __is_signed_or_unsigned_integer<_Tp>::value
     constexpr _Res
     saturate_cast(_Tp __x) noexcept
     {
index 1de2d18dc1aa820e9f0bbab22ef0f7755a354d30..5bdf4dc7b24b3e437291d1e798057deff74244aa 100644 (file)
 namespace __gnu_cxx _GLIBCXX_VISIBILITY(default)
 {
 #endif
-/// @cond undocumented
-namespace __detail
-{
-  template<typename _Tp>
-    concept __cv_unqual_signed_or_unsigned_integer_type
-      = std::same_as<_Tp, std::remove_cv_t<_Tp>>
-         && std::__is_standard_integer<_Tp>::value;
-}
-/// @endcond
 
 /** Checked integer arithmetic
  *
@@ -71,10 +62,9 @@ template<typename _Tp1, typename _Tp2, typename _Tp3>
   inline bool
   ckd_add(_Tp1* __result, _Tp2 __a, _Tp3 __b)
   {
-    using __gnu_cxx::__detail::__cv_unqual_signed_or_unsigned_integer_type;
-    static_assert(__cv_unqual_signed_or_unsigned_integer_type<_Tp1>);
-    static_assert(__cv_unqual_signed_or_unsigned_integer_type<_Tp2>);
-    static_assert(__cv_unqual_signed_or_unsigned_integer_type<_Tp3>);
+    static_assert(std::__is_signed_or_unsigned_integer<_Tp1>::value);
+    static_assert(std::__is_signed_or_unsigned_integer<_Tp2>::value);
+    static_assert(std::__is_signed_or_unsigned_integer<_Tp3>::value);
     return __builtin_add_overflow(__a, __b, __result);
   }
 
@@ -82,10 +72,9 @@ template<typename _Tp1, typename _Tp2, typename _Tp3>
   inline bool
   ckd_sub(_Tp1* __result, _Tp2 __a, _Tp3 __b)
   {
-    using __gnu_cxx::__detail::__cv_unqual_signed_or_unsigned_integer_type;
-    static_assert(__cv_unqual_signed_or_unsigned_integer_type<_Tp1>);
-    static_assert(__cv_unqual_signed_or_unsigned_integer_type<_Tp2>);
-    static_assert(__cv_unqual_signed_or_unsigned_integer_type<_Tp3>);
+    static_assert(std::__is_signed_or_unsigned_integer<_Tp1>::value);
+    static_assert(std::__is_signed_or_unsigned_integer<_Tp2>::value);
+    static_assert(std::__is_signed_or_unsigned_integer<_Tp3>::value);
     return __builtin_sub_overflow(__a, __b, __result);
   }
 
@@ -93,15 +82,14 @@ template<typename _Tp1, typename _Tp2, typename _Tp3>
   inline bool
   ckd_mul(_Tp1* __result, _Tp2 __a, _Tp3 __b)
   {
-    using __gnu_cxx::__detail::__cv_unqual_signed_or_unsigned_integer_type;
-    static_assert(__cv_unqual_signed_or_unsigned_integer_type<_Tp1>);
-    static_assert(__cv_unqual_signed_or_unsigned_integer_type<_Tp2>);
-    static_assert(__cv_unqual_signed_or_unsigned_integer_type<_Tp3>);
+    static_assert(std::__is_signed_or_unsigned_integer<_Tp1>::value);
+    static_assert(std::__is_signed_or_unsigned_integer<_Tp2>::value);
+    static_assert(std::__is_signed_or_unsigned_integer<_Tp3>::value);
     return __builtin_mul_overflow(__a, __b, __result);
   }
 /// @}
 #ifndef _GLIBCXX_DOXYGEN
-}
+} // namespace __gnu_cxx
 
 using __gnu_cxx::ckd_add;
 using __gnu_cxx::ckd_sub;
index 6786dc6a71bc2cc0f938b358d9fc0bf014e9c417..78cb8e3987567ef49fc0442a2f614a0165efbf38 100644 (file)
@@ -48,7 +48,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
   // or is_signed, digits10, max_digits10, or max_exponent10 for floats.
 
   // Unlike __is_integer (and std::is_integral) this trait is true for
-  // non-standard built-in integer types such as __int128 and __int20.
+  // non-standard built-in integer types such as __int20.
   template<typename _Tp>
     struct __is_integer_nonstrict
     : public std::__is_integer<_Tp>
index 8cf2c0b01d7c653e61b80d4002d0a05cf0ee621f..47f5aaa0f7cdddf8805d4797e46afacf6fd4c6a1 100644 (file)
@@ -554,10 +554,10 @@ namespace __detail
 
 } // namespace __detail
 
-  /// std::from_chars for integral types.
+  /// std::from_chars for integer types.
   template<typename _Tp,
-          enable_if_t<__or_<__is_standard_integer<_Tp>,
-                            is_same<char, remove_cv_t<_Tp>>>::value, int> = 0>
+          enable_if_t<__or_<__is_signed_or_unsigned_integer<_Tp>,
+                            is_same<char, _Tp>>::value, int> = 0>
     _GLIBCXX23_CONSTEXPR from_chars_result
     from_chars(const char* __first, const char* __last, _Tp& __value,
               int __base = 10)
index 03cc4f02a1cd37a603d9f70b73c1cf785f3e292b..981fa1c601a1752cd35f655a3e6220b80e41838d 100644 (file)
@@ -352,11 +352,11 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
   template<typename _OffsetType, typename _ExtentType, typename _StrideType>
     struct strided_slice
     {
-      static_assert(__is_standard_integer<_OffsetType>::value
+      static_assert(__is_signed_or_unsigned_integer<_OffsetType>::value
        || __detail::__integral_constant_like<_OffsetType>);
-      static_assert(__is_standard_integer<_ExtentType>::value
+      static_assert(__is_signed_or_unsigned_integer<_ExtentType>::value
        || __detail::__integral_constant_like<_ExtentType>);
-      static_assert(__is_standard_integer<_StrideType>::value
+      static_assert(__is_signed_or_unsigned_integer<_StrideType>::value
        || __detail::__integral_constant_like<_StrideType>);
 
       using offset_type = _OffsetType;
@@ -379,7 +379,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
   template<typename _IndexType, size_t... _Extents>
     class extents
     {
-      static_assert(__is_standard_integer<_IndexType>::value,
+      static_assert(__is_signed_or_unsigned_integer<_IndexType>::value,
                    "IndexType must be a signed or unsigned integer type");
       static_assert(
          (__mdspan::__valid_static_extent<_Extents, _IndexType> && ...),
index 7c157ea7ba196ef350796de140b474cfea3212ca..3f0bcc4e77d2b08837031a2eeb17e66036797437 100644 (file)
@@ -826,7 +826,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
   // Check if a type is one of the signed integer types.
   __extension__
   template<typename _Tp>
-    using __is_signed_integer = __is_one_of<__remove_cv_t<_Tp>,
+    using __is_signed_integer = __is_one_of<_Tp,
          signed char, signed short, signed int, signed long,
          signed long long
 #if defined(__GLIBCXX_TYPE_INT_N_0)
@@ -840,13 +840,16 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 #endif
 #if defined(__GLIBCXX_TYPE_INT_N_3)
          , signed __GLIBCXX_TYPE_INT_N_3
+#endif
+#if defined __STRICT_ANSI__ && defined __SIZEOF_INT128__
+         , signed __int128
 #endif
          >;
 
   // Check if a type is one of the unsigned integer types.
   __extension__
   template<typename _Tp>
-    using __is_unsigned_integer = __is_one_of<__remove_cv_t<_Tp>,
+    using __is_unsigned_integer = __is_one_of<_Tp,
          unsigned char, unsigned short, unsigned int, unsigned long,
          unsigned long long
 #if defined(__GLIBCXX_TYPE_INT_N_0)
@@ -860,12 +863,16 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 #endif
 #if defined(__GLIBCXX_TYPE_INT_N_3)
          , unsigned __GLIBCXX_TYPE_INT_N_3
+#endif
+#if defined __STRICT_ANSI__ && defined __SIZEOF_INT128__
+         , unsigned __int128
 #endif
          >;
 
   // Check if a type is one of the signed or unsigned integer types.
+  // i.e. an integral type except bool, char, wchar_t, and charN_t.
   template<typename _Tp>
-    using __is_standard_integer
+    using __is_signed_or_unsigned_integer
       = __or_<__is_signed_integer<_Tp>, __is_unsigned_integer<_Tp>>;
 
   // __void_t (std::void_t for C++11)
diff --git a/libstdc++-v3/testsuite/20_util/integer_comparisons/extended.cc b/libstdc++-v3/testsuite/20_util/integer_comparisons/extended.cc
new file mode 100644 (file)
index 0000000..d862b16
--- /dev/null
@@ -0,0 +1,60 @@
+// { dg-do compile { target c++20 } }
+
+#include <utility>
+
+template<typename T>
+constexpr bool
+test()
+{
+  using S = std::make_signed_t<T>;
+  using U = std::make_unsigned_t<T>;
+
+  static_assert( std::cmp_less((S)-1, (U)1));
+  static_assert( ! std::cmp_less((S)20, (U)10));
+  static_assert( ! std::cmp_less((U)20, (S)10));
+
+  static_assert( std::cmp_greater((S)100, (U)1) );
+  static_assert( std::cmp_greater((U)100, (S)1) );
+  static_assert( ! std::cmp_greater((S)-100, (U)1) );
+
+  static_assert( std::cmp_less_equal((S)-1, (U)1));
+  static_assert( std::cmp_less_equal((U)10, (S)10));
+  static_assert( ! std::cmp_less_equal((U)-100, (S)-100));
+
+  static_assert( std::cmp_greater_equal((S)200, (U)2) );
+  static_assert( std::cmp_greater_equal((U)2000, (S)2000) );
+  static_assert( ! std::cmp_greater_equal((S)-100, (U)100) );
+
+  static_assert( std::cmp_equal((S)1, (U)1) );
+  static_assert( ! std::cmp_equal((S)-2, (U)-2) );
+
+  static_assert( std::cmp_not_equal((S)-1, (U)-1) );
+  static_assert( ! std::cmp_not_equal((S)100, (U)100) );
+
+  static_assert( std::in_range<S>((U)5) );
+  static_assert( std::in_range<U>((S)5) );
+  static_assert( ! std::in_range<S>((U)-5) );
+  static_assert( ! std::in_range<U>((S)-5) );
+
+  return true;
+}
+
+#ifdef __SIZEOF_INT128__
+static_assert(test<__int128>());
+#endif
+
+#ifdef __GLIBCXX_TYPE_INT_N_0
+static_assert(test<__GLIBCXX_TYPE_INT_N_0>());
+#endif
+
+#ifdef __GLIBCXX_TYPE_INT_N_1
+static_assert(test<__GLIBCXX_TYPE_INT_N_1>());
+#endif
+
+#ifdef __GLIBCXX_TYPE_INT_N_2
+static_assert(test<__GLIBCXX_TYPE_INT_N_2>());
+#endif
+
+#ifdef __GLIBCXX_TYPE_INT_N_3
+static_assert(test<__GLIBCXX_TYPE_INT_N_3>());
+#endif
index d5f07c106aabcc553de05a2d69d00cb7b03a7e69..fdbcb707bbe049b6350551814fa2fd2f5c5c181c 100644 (file)
@@ -16,6 +16,13 @@ static_assert(!std::is_constructible_v<std::extents<int, 1, dyn, 3>, int, int>);
 // Not constructible from non integer-like objects.
 static_assert(!std::is_constructible_v<std::extents<int, 1>, int, A>);
 
+#ifdef __SIZEOF_INT128__
+static_assert(std::is_constructible_v<std::extents<__int128, 1, 2>,
+                                     __int128, unsigned __int128>);
+static_assert(std::is_constructible_v<std::extents<unsigned __int128, 1, 2>,
+                                     unsigned int, int>);
+#endif
+
 // No implicit conversion from integer-like objects.
 template<typename Extent, typename... OExtents>
   constexpr bool
index c43a8214321406044f306eb9e0c67b374a085770..6fa5aaa2f1ed47d405144a836417cb05477c055f 100644 (file)
@@ -34,6 +34,9 @@ test_all()
   test_initializers(0, 1, 2);
   test_initializers(std::integral_constant<short, 0>{}, size_t{1}, std::cw<2>);
   test_initializers(-1, 2, 2);
+#ifdef __SIZEOF_INT128__
+  test_initializers((__int128)1, (unsigned __int128)-2, std::cw<(__int128)3>);
+#endif
   return true;
 }
 
diff --git a/libstdc++-v3/testsuite/26_numerics/saturation/extended.cc b/libstdc++-v3/testsuite/26_numerics/saturation/extended.cc
new file mode 100644 (file)
index 0000000..fbef628
--- /dev/null
@@ -0,0 +1,55 @@
+// { dg-do compile { target c++26 } }
+
+#include <numeric>
+#include <limits>
+
+template<typename T>
+constexpr bool
+test()
+{
+  using S = std::make_signed_t<T>;
+  using U = std::make_unsigned_t<T>;
+
+  constexpr S smax = std::numeric_limits<S>::max();
+  constexpr S smin = std::numeric_limits<S>::min();
+  constexpr U umax = std::numeric_limits<U>::max();
+
+  static_assert( std::add_sat(smax, (S)1) == smax );
+  static_assert( std::add_sat(smin, (S)-2) == smin );
+  static_assert( std::add_sat(umax, (U)3) == umax );
+
+  static_assert( std::sub_sat(smax, (S)-1) == smax );
+  static_assert( std::sub_sat(smin, (S)2) == smin );
+  static_assert( std::sub_sat((U)0, (U)3) == (U)0 );
+
+  static_assert( std::mul_sat((S)(smax >> 1), (S)3) == smax );
+  static_assert( std::mul_sat((S)(smin >> 1), (S)5) == smin );
+  static_assert( std::mul_sat((U)(umax >> 1), (U)7) == umax );
+
+  static_assert( std::div_sat(smax, (S)2) == (smax >> 1) );
+  static_assert( std::div_sat(smin, (S)4) == (smin >> 2) );
+  static_assert( std::div_sat(smin, (S)-1) == smax );
+  static_assert( std::div_sat(umax, (U)8) == (umax >> 3) );
+
+  return true;
+}
+
+#ifdef __SIZEOF_INT128__
+static_assert(test<__int128>());
+#endif
+
+#ifdef __GLIBCXX_TYPE_INT_N_0
+static_assert(test<__GLIBCXX_TYPE_INT_N_0>());
+#endif
+
+#ifdef __GLIBCXX_TYPE_INT_N_1
+static_assert(test<__GLIBCXX_TYPE_INT_N_1>());
+#endif
+
+#ifdef __GLIBCXX_TYPE_INT_N_2
+static_assert(test<__GLIBCXX_TYPE_INT_N_2>());
+#endif
+
+#ifdef __GLIBCXX_TYPE_INT_N_3
+static_assert(test<__GLIBCXX_TYPE_INT_N_3>());
+#endif
diff --git a/libstdc++-v3/testsuite/26_numerics/stdckdint/extended.cc b/libstdc++-v3/testsuite/26_numerics/stdckdint/extended.cc
new file mode 100644 (file)
index 0000000..efc0792
--- /dev/null
@@ -0,0 +1,65 @@
+// { dg-do run { target c++26 } }
+
+#include <stdckdint.h>
+#include <limits>
+#include <testsuite_hooks.h>
+
+template<typename T>
+void
+test()
+{
+  using S = std::make_signed_t<T>;
+  using U = std::make_unsigned_t<T>;
+
+  constexpr S smax = std::numeric_limits<S>::max();
+  constexpr S smin = std::numeric_limits<S>::min();
+  constexpr U umax = std::numeric_limits<U>::max();
+  S sout{};
+  U uout{};
+
+  VERIFY( ckd_add(&sout, smax, (S)1) );
+  VERIFY( ! ckd_add(&uout, smax, (U)1) && uout == ((U)smax + (U)1) );
+  VERIFY( ! ckd_add(&sout, smin, (S)99) && sout == (smin + 99) );
+  VERIFY( ckd_add(&uout, smin, (S)99) );
+  VERIFY( ckd_add(&sout, smin, (S)-2) );
+  VERIFY( ckd_add(&uout, umax, (U)3) );
+  VERIFY( ! ckd_add(&sout, (U)9, (U)3) && sout == 12 );
+
+  VERIFY( ckd_sub(&sout, smax, (S)-1) );
+  VERIFY( ! ckd_sub(&uout, smax, (S)-1) && uout == ((U)smax + (U)1) );
+  VERIFY( ckd_sub(&sout, smin, (S)2) );
+  VERIFY( ! ckd_sub(&sout, smin, (S)-2) && sout == (smin + 2) );
+  VERIFY( ! ckd_sub(&sout, (U)0, (U)3) && sout == -3 );
+  VERIFY( ckd_sub(&uout, (U)0, (U)3) );
+
+  VERIFY( ! ckd_mul(&sout, (S)(smax >> 2), (S)3) && sout == (smax/4*3) );
+  VERIFY( ckd_mul(&sout, (S)(smax >> 1), (S)3) );
+  VERIFY( ! ckd_mul(&uout, (S)(smax >> 1), (S)3) );
+  VERIFY( ckd_mul(&sout, (S)(smin >> 1), (S)5) );
+  VERIFY( ! ckd_mul(&uout, (U)(umax >> 2), (U)3) );
+  VERIFY( ckd_mul(&sout, (U)(umax >> 2), (U)3) );
+  VERIFY( ckd_mul(&uout, (U)(umax >> 1), (U)7) );
+}
+
+int main()
+{
+#ifdef __SIZEOF_INT128__
+  test<__int128>();
+#endif
+
+#ifdef __GLIBCXX_TYPE_INT_N_0
+  test<__GLIBCXX_TYPE_INT_N_0>();
+#endif
+
+#ifdef __GLIBCXX_TYPE_INT_N_1
+  test<__GLIBCXX_TYPE_INT_N_1>();
+#endif
+
+#ifdef __GLIBCXX_TYPE_INT_N_2
+  test<__GLIBCXX_TYPE_INT_N_2>();
+#endif
+
+#ifdef __GLIBCXX_TYPE_INT_N_3
+  test<__GLIBCXX_TYPE_INT_N_3>();
+#endif
+}