]> git.ipfire.org Git - thirdparty/gcc.git/commitdiff
libstdc++: Rework constant_wrapper assignments and increments
authorTomasz Kamiński <tkaminsk@redhat.com>
Wed, 18 Feb 2026 09:38:04 +0000 (10:38 +0100)
committerTomasz Kamiński <tkaminsk@redhat.com>
Thu, 19 Feb 2026 16:10:19 +0000 (17:10 +0100)
This implements LWG4383 with LWG4500, LWG4523 follow-up corrections.

This patch changes the constant_wrapper assignments operator (including
compounds), increment and decrement to apply directly to value. In
consequence the operators are only supported for types, for which above
operations can be applied on const value.

libstdc++-v3/ChangeLog:

* include/std/type_traits (_CWOperators::operator++)
(_CWOperators::operator--, _CWOperators::operator+=)
(_CWOperators::operator-=, _CWOperators::operator*=)
(_CWOperators::operator/=, _CWOperators::operator%=)
(_CWOperators::operator&=, _CWOperators::operator|=)
(_CWOperators::operator^=, _CWOperators::operator<<=)
(_CWOperators::operator>>=, constant_wrapper::operator=):
Adjust definitions to apply operator on value.
* testsuite/20_util/constant_wrapper/generic.cc:
Remove test_pseudo_mutator.
* testsuite/20_util/constant_wrapper/instantiate.cc:
Test that operators are not provided if wrapped type
do not support them, or provide mutable operators.

libstdc++-v3/include/std/type_traits
libstdc++-v3/testsuite/20_util/constant_wrapper/generic.cc
libstdc++-v3/testsuite/20_util/constant_wrapper/instantiate.cc

index ea700d1ed97d2e404fcceff017577229ff36cfac..a7f881fe97367357a9ba0b25e0eea3084bb4816e 100644 (file)
@@ -4606,128 +4606,86 @@ template<typename _Ret, typename _Fn, typename... _Args>
     template<_ConstExprParam _Tp>
       constexpr auto
       operator++(this _Tp) noexcept
-      requires requires(_Tp::value_type __x) { ++__x; }
-      {
-       return constant_wrapper<
-         [] { auto __x = _Tp::value; return ++__x; }()>{};
-      }
+       -> constant_wrapper<(++_Tp::value)>
+      { return {}; }
 
     template<_ConstExprParam _Tp>
       constexpr auto
       operator++(this _Tp, int) noexcept
-      requires requires(_Tp::value_type __x) { __x++; }
-      {
-       return constant_wrapper<
-         [] { auto __x = _Tp::value; return __x++; }()>{};
-      }
+       -> constant_wrapper<(_Tp::value++)>
+      { return {}; }
 
     template<_ConstExprParam _Tp>
       constexpr auto
       operator--(this _Tp) noexcept
-      requires requires(_Tp::value_type __x) { --__x; }
-      {
-       return constant_wrapper<
-         [] { auto __x = _Tp::value; return --__x; }()>{};
-      }
+       -> constant_wrapper<(--_Tp::value)>
+      { return {}; }
 
     template<_ConstExprParam _Tp>
       constexpr auto
       operator--(this _Tp, int) noexcept
-      requires requires(_Tp::value_type __x) { __x--; }
-      {
-       return constant_wrapper<
-         [] { auto __x = _Tp::value; return __x--; }()>{};
-      }
+       -> constant_wrapper<(_Tp::value--)>
+      { return {}; }
 
     template<_ConstExprParam _Tp, _ConstExprParam _Right>
       constexpr auto
       operator+=(this _Tp, _Right) noexcept
-      requires requires(_Tp::value_type __x) { __x += _Right::value; }
-      {
-       return constant_wrapper<
-         [] { auto __x = _Tp::value; return __x += _Right::value; }()>{};
-      }
+       -> constant_wrapper<(_Tp::value += _Right::value)>
+      { return {}; }
 
     template<_ConstExprParam _Tp, _ConstExprParam _Right>
       constexpr auto
       operator-=(this _Tp, _Right) noexcept
-      requires requires(_Tp::value_type __x) { __x -= _Right::value; }
-      {
-       return constant_wrapper<
-         [] { auto __x = _Tp::value; return __x -= _Right::value; }()>{};
-      }
+       -> constant_wrapper<(_Tp::value -= _Right::value)>
+      { return {}; }
 
     template<_ConstExprParam _Tp, _ConstExprParam _Right>
       constexpr auto
       operator*=(this _Tp, _Right) noexcept
-      requires requires(_Tp::value_type __x) { __x *= _Right::value; }
-      {
-       return constant_wrapper<
-         [] { auto __x = _Tp::value; return __x *= _Right::value; }()>{};
-      }
+       -> constant_wrapper<(_Tp::value *= _Right::value)>
+      { return {}; }
 
     template<_ConstExprParam _Tp, _ConstExprParam _Right>
       constexpr auto
       operator/=(this _Tp, _Right) noexcept
-      requires requires(_Tp::value_type __x) { __x /= _Right::value; }
-      {
-       return constant_wrapper<
-         [] { auto __x = _Tp::value; return __x /= _Right::value; }()>{};
-      }
+       -> constant_wrapper<(_Tp::value /= _Right::value)>
+      { return {}; }
 
     template<_ConstExprParam _Tp, _ConstExprParam _Right>
       constexpr auto
       operator%=(this _Tp, _Right) noexcept
-      requires requires(_Tp::value_type __x) { __x %= _Right::value; }
-      {
-       return constant_wrapper<
-         [] { auto __x = _Tp::value; return __x %= _Right::value; }()>{};
-      }
+       -> constant_wrapper<(_Tp::value %= _Right::value)>
+      { return {}; }
 
     template<_ConstExprParam _Tp, _ConstExprParam _Right>
       constexpr auto
       operator&=(this _Tp, _Right) noexcept
-      requires requires(_Tp::value_type __x) { __x &= _Right::value; }
-      {
-       return constant_wrapper<
-         [] { auto __x = _Tp::value; return __x &= _Right::value; }()>{};
-      }
+       -> constant_wrapper<(_Tp::value &= _Right::value)>
+      { return {}; }
 
     template<_ConstExprParam _Tp, _ConstExprParam _Right>
       constexpr auto
       operator|=(this _Tp, _Right) noexcept
-      requires requires(_Tp::value_type __x) { __x |= _Right::value; }
-      {
-       return constant_wrapper<
-         [] { auto __x = _Tp::value; return __x |= _Right::value; }()>{};
-      }
+       -> constant_wrapper<(_Tp::value |= _Right::value)>
+      { return {}; }
 
     template<_ConstExprParam _Tp, _ConstExprParam _Right>
       constexpr auto
       operator^=(this _Tp, _Right) noexcept
-      requires requires(_Tp::value_type __x) { __x ^= _Right::value; }
-      {
-       return constant_wrapper<
-         [] { auto __x = _Tp::value; return __x ^= _Right::value; }()>{};
-      }
+       -> constant_wrapper<(_Tp::value ^= _Right::value)>
+      { return {}; }
 
     template<_ConstExprParam _Tp, _ConstExprParam _Right>
       constexpr auto
       operator<<=(this _Tp, _Right) noexcept
-      requires requires(_Tp::value_type __x) { __x <<= _Right::value; }
-      {
-       return constant_wrapper<
-         [] { auto __x = _Tp::value; return __x <<= _Right::value; }()>{};
-      }
+       -> constant_wrapper<(_Tp::value <<= _Right::value)>
+      { return {}; }
 
     template<_ConstExprParam _Tp, _ConstExprParam _Right>
       constexpr auto
       operator>>=(this _Tp, _Right) noexcept
-      requires requires(_Tp::value_type __x) { __x >>= _Right::value; }
-      {
-       return constant_wrapper<
-         [] { auto __x = _Tp::value; return __x >>= _Right::value; }()>{};
-      }
+       -> constant_wrapper<(_Tp::value >>= _Right::value)>
+      { return {}; }
   };
 
   template<_CwFixedValue _Xv, typename>
@@ -4740,11 +4698,8 @@ template<typename _Ret, typename _Fn, typename... _Args>
     template<_ConstExprParam _Right>
       constexpr auto
       operator=(_Right) const noexcept
-      requires requires(value_type __x) { __x = _Right::value; }
-      {
-       return constant_wrapper<
-         [] { auto __x = value; return __x = _Right::value; }()>{};
-      }
+       -> constant_wrapper<(value = _Right::value)>
+      { return {}; }
 
     constexpr
     operator decltype(value)() const noexcept
index f632f8e285a6e883a827dabac450b76710050f4e..de4334b64f5ba510dc4ab8094ab36f454989db91 100644 (file)
@@ -241,19 +241,6 @@ test_member_pointer()
   check_same(((&decltype(co)::value)->*cdiv)(denom), expect_unwrapped);
 }
 
-constexpr void
-test_pseudo_mutator()
-{
-  auto ci = std::cw<3>;
-  auto cmmi = --ci;
-  VERIFY(ci.value == 3);
-  VERIFY(cmmi.value == 2);
-
-  auto cimm = ci--;
-  VERIFY(ci.value == 3);
-  VERIFY(cimm.value == 3);
-}
-
 struct Truthy
 {
   constexpr operator bool() const
@@ -375,7 +362,6 @@ test_all()
   test_indexable2();
   test_indexable3();
   test_member_pointer();
-  test_pseudo_mutator();
   test_logic();
   test_three_way();
   test_equality();
index 4f1232598d60a5689e5116c71c1b76005c144551..5adf6fda5a382aa8044f46831bb708f586404fde 100644 (file)
@@ -98,40 +98,85 @@ namespace member_ops
     };
 }
 
+namespace mutable_ops
+{
+  template<int OpId>
+    struct UnaryOps
+    {
+      constexpr int
+      operator+() noexcept requires (OpId == 0)
+      { return OpId; }
+
+      constexpr int
+      operator-() noexcept requires (OpId == 1)
+      { return OpId; }
+
+      constexpr int
+      operator~() noexcept requires (OpId == 2)
+      { return OpId; }
+
+      constexpr int
+      operator!() noexcept requires (OpId == 3)
+      { return OpId; }
+
+      constexpr int
+      operator&() noexcept requires (OpId == 4)
+      { return OpId; }
+
+      constexpr int
+      operator*() noexcept requires (OpId == 5)
+      { return OpId; }
+
+      constexpr int
+      operator++() noexcept requires (OpId == 6)
+      { return OpId; }
+
+      constexpr int
+      operator++(int) noexcept requires (OpId == 7)
+      { return OpId; }
+
+      constexpr int
+      operator--() noexcept requires (OpId == 8)
+      { return OpId; }
+
+      constexpr int
+      operator--(int) noexcept requires (OpId == 9)
+      { return OpId; }
+    };
+}
+
 constexpr size_t n_unary_ops = 10;
 
-template<template<int> typename Ops, int OpId>
+template<template<int> typename Ops, int EnabledId, int ActiveId = EnabledId>
   constexpr void
   test_unary_operator()
   {
-    auto x = std::cw<Ops<OpId>{}>;
+    auto x = std::cw<Ops<EnabledId>{}>;
 
     auto check = [](auto c)
     {
-      VERIFY(c == OpId);
-      static_assert(std::same_as<decltype(c), std::constant_wrapper<OpId>>);
+      VERIFY(c == EnabledId);
+      static_assert(std::same_as<decltype(c), std::constant_wrapper<EnabledId>>);
     };
 
-    if constexpr (OpId == 0)
-      check(+x);
-    if constexpr (OpId == 1)
-      check(-x);
-    if constexpr (OpId == 2)
-      check(~x);
-    if constexpr (OpId == 3)
-      check(!x);
-    if constexpr (OpId == 4)
+#define CHECK_EXPR(Id, Expr)      \
+    if constexpr (ActiveId == Id) \
+      check(Expr);                \
+    else                          \
+      static_assert(!requires { Expr; })
+
+    CHECK_EXPR(0, +x);
+    CHECK_EXPR(1, -x);
+    CHECK_EXPR(2, ~x);
+    CHECK_EXPR(3, !x);
+    if constexpr (ActiveId == 4)
       check(&x);
-    if constexpr (OpId == 5)
-      check(*x);
-    if constexpr (OpId == 6)
-      check(++x);
-    if constexpr (OpId == 7)
-      check(x++);
-    if constexpr (OpId == 8)
-      check(--x);
-    if constexpr (OpId == 9)
-      check(x--);
+    CHECK_EXPR(5, *x);
+    CHECK_EXPR(6, ++x);
+    CHECK_EXPR(7, x++);
+    CHECK_EXPR(8, --x);
+    CHECK_EXPR(9, x--);
+#undef CHECK_EXPR
 
     static_assert(n_unary_ops == 10);
   }
@@ -143,6 +188,7 @@ test_unary_operators_all()
   {
     (test_unary_operator<free_ops::UnaryOps, Idx>(), ...);
     (test_unary_operator<member_ops::UnaryOps, Idx>(), ...);
+    (test_unary_operator<mutable_ops::UnaryOps, Idx, -1>(), ...);
   };
   run(std::make_index_sequence<n_unary_ops>());
 }
@@ -393,77 +439,187 @@ namespace member_ops
     };
 }
 
+namespace mutable_ops
+{
+  template<int OpId>
+    struct BinaryOps
+    {
+      constexpr int
+      operator+(BinaryOps) noexcept requires (OpId == 0)
+      { return OpId; }
+
+      constexpr int
+      operator-(BinaryOps) noexcept requires (OpId == 1)
+      { return OpId; }
+
+      constexpr int
+      operator*(BinaryOps) noexcept requires (OpId == 2)
+      { return OpId; }
+
+      constexpr int
+      operator/(BinaryOps) noexcept requires (OpId == 3)
+      { return OpId; }
+
+      constexpr int
+      operator%(BinaryOps) noexcept requires (OpId == 4)
+      { return OpId; }
+
+      constexpr int
+      operator<<(BinaryOps) noexcept requires (OpId == 5)
+      { return OpId; }
+
+      constexpr int
+      operator>>(BinaryOps) noexcept requires (OpId == 6)
+      { return OpId; }
+
+      constexpr int
+      operator&(BinaryOps) noexcept requires (OpId == 7)
+      { return OpId; }
+
+      constexpr int
+      operator|(BinaryOps) noexcept requires (OpId == 8)
+      { return OpId; }
+
+      constexpr int
+      operator^(BinaryOps) noexcept requires (OpId == 9)
+      { return OpId; }
+
+      constexpr int
+      operator&&(BinaryOps) noexcept requires (OpId == 10)
+      { return OpId; }
+
+      constexpr int
+      operator||(BinaryOps) noexcept requires (OpId == 11)
+      { return OpId; }
+
+      constexpr int
+      operator<=>(BinaryOps) noexcept requires (OpId == 12)
+      { return OpId; }
+
+      constexpr int
+      operator<(BinaryOps) noexcept requires (OpId == 13)
+      { return OpId; }
+
+      constexpr int
+      operator<=(BinaryOps) noexcept requires (OpId == 14)
+      { return OpId; }
+
+      constexpr int
+      operator==(BinaryOps) noexcept requires (OpId == 15)
+      { return OpId; }
+
+      constexpr int
+      operator!=(BinaryOps) noexcept requires (OpId == 16)
+      { return OpId; }
+
+      constexpr int
+      operator>(BinaryOps) noexcept requires (OpId == 17)
+      { return OpId; }
+
+      constexpr int
+      operator>=(BinaryOps) noexcept requires (OpId == 18)
+      { return OpId; }
+
+      constexpr int
+      operator+=(BinaryOps) noexcept requires (OpId == 19)
+      { return OpId; }
+
+      constexpr int
+      operator-=(BinaryOps) noexcept requires (OpId == 20)
+      { return OpId; }
+
+      constexpr int
+      operator*=(BinaryOps) noexcept requires (OpId == 21)
+      { return OpId; }
+
+      constexpr int
+      operator/=(BinaryOps) noexcept requires (OpId == 22)
+      { return OpId; }
+
+      constexpr int
+      operator%=(BinaryOps) noexcept requires (OpId == 23)
+      { return OpId; }
+
+      constexpr int
+      operator&=(BinaryOps) noexcept requires (OpId == 24)
+      { return OpId; }
+
+      constexpr int
+      operator|=(BinaryOps) noexcept requires (OpId == 25)
+      { return OpId; }
+
+      constexpr int
+      operator^=(BinaryOps) noexcept requires (OpId == 26)
+      { return OpId; }
+
+      constexpr int
+      operator<<=(BinaryOps) noexcept requires (OpId == 27)
+      { return OpId; }
+
+      constexpr int
+      operator>>=(BinaryOps) noexcept requires (OpId == 28)
+      { return OpId; }
+    };
+}
+
 constexpr size_t n_binary_ops = 29;
 
-template<template<int> typename Ops, int OpId>
+template<template<int> typename Ops, int EnabledId, int ActiveId = EnabledId>
   constexpr void
   test_binary_operator()
   {
-    auto cx = std::cw<Ops<OpId>{}>;
-    auto cy = std::cw<Ops<OpId>{}>;
+    auto cx = std::cw<Ops<EnabledId>{}>;
+    auto cy = std::cw<Ops<EnabledId>{}>;
 
-    auto check = [](auto c)
+    auto check = []<typename ResultT>(auto c, ResultT)
     {
-      VERIFY(c == OpId);
-      static_assert(std::same_as<decltype(c), std::constant_wrapper<OpId>>);
+      VERIFY(c == ResultT::value);
+      static_assert(std::same_as<decltype(c), ResultT>);
     };
 
-    if constexpr (OpId == 0)
-      check(cx + cy);
-    if constexpr (OpId == 1)
-      check(cx - cy);
-    if constexpr (OpId == 2)
-      check(cx * cy);
-    if constexpr (OpId == 3)
-      check(cx / cy);
-    if constexpr (OpId == 4)
-      check(cx % cy);
-    if constexpr (OpId == 5)
-      check(cx << cy);
-    if constexpr (OpId == 6)
-      check(cx >> cy);
-    if constexpr (OpId == 7)
-      check(cx & cy);
-    if constexpr (OpId == 8)
-      check(cx | cy);
-    if constexpr (OpId == 10)
-      check(cx && cy);
-    if constexpr (OpId == 11)
-      check(cx || cy);
-    if constexpr (OpId == 12)
-      check(cx <=> cy);
-    if constexpr (OpId == 13)
-      check(cx < cy);
-    if constexpr (OpId == 14)
-      check(cx <= cy);
-    if constexpr (OpId == 15)
-      check(cx == cy);
-    if constexpr (OpId == 16)
-      check(cx != cy);
-    if constexpr (OpId == 17)
-      check(cx > cy);
-    if constexpr (OpId == 18)
-      check(cx >= cy);
-    if constexpr (OpId == 19)
-      check(cx += cy);
-    if constexpr (OpId == 20)
-      check(cx -= cy);
-    if constexpr (OpId == 21)
-      check(cx *= cy);
-    if constexpr (OpId == 22)
-      check(cx /= cy);
-    if constexpr (OpId == 23)
-      check(cx %= cy);
-    if constexpr (OpId == 24)
-      check(cx &= cy);
-    if constexpr (OpId == 25)
-      check(cx |= cy);
-    if constexpr (OpId == 26)
-      check(cx ^= cy);
-    if constexpr (OpId == 27)
-      check(cx <<= cy);
-    if constexpr (OpId == 28)
-      check(cx >>= cy);
+#define CHECK_OP(Id, Op)            \
+    if constexpr (ActiveId == Id)   \
+      check(cx Op cy, std::cw<Id>); \
+    else                            \
+      static_assert(!requires { cx Op cy; })
+
+#define CHECK_OP_F(Id, Op, Fb)             \
+    if constexpr (ActiveId == Fb)          \
+      check(cx Op cy, std::cw<(Fb Op 0)>); \
+    else CHECK_OP(Id, Op)
+
+    CHECK_OP( 0, +);
+    CHECK_OP( 1, -);
+    CHECK_OP( 2, *);
+    CHECK_OP( 3, /);
+    CHECK_OP( 4, %);
+    CHECK_OP( 5, <<);
+    CHECK_OP( 6, >>);
+    CHECK_OP( 7, &);
+    CHECK_OP( 8, |);
+    CHECK_OP( 9, ^);
+    CHECK_OP(10, &&);
+    CHECK_OP(11, ||);
+    CHECK_OP(12, <=>);
+    CHECK_OP_F(13, <,  12);
+    CHECK_OP_F(14, <=, 12);
+    CHECK_OP_F(17, >,  12);
+    CHECK_OP_F(18, >=, 12);
+    CHECK_OP(15, ==);
+    CHECK_OP(16, !=);
+    CHECK_OP(19, +=);
+    CHECK_OP(20, -=);
+    CHECK_OP(21, *=);
+    CHECK_OP(22, /=);
+    CHECK_OP(23, %=);
+    CHECK_OP(24, &=);
+    CHECK_OP(25, |=);
+    CHECK_OP(26, ^=);
+    CHECK_OP(27, <<=);
+    CHECK_OP(28, >>=);
+#undef CHECK_OP_F
+#undef CHECK_OP
+
     static_assert(n_binary_ops == 29);
   }
 
@@ -476,74 +632,58 @@ template<template<int> typename Ops, int OpId>
     constexpr auto y = Ops<OpId>{};
     auto cy = std::cw<y>;
 
-    auto check = [](auto vc, auto cv)
+    auto check = []<typename ResT>(auto vc, auto cv, ResT res)
     {
-      auto impl = [](auto c)
-      {
-       VERIFY(c == OpId);
-       static_assert(std::same_as<decltype(c), int>);
-      };
-
-      impl(vc);
-      impl(cv);
+      VERIFY(vc == res);
+      static_assert(std::same_as<decltype(vc), ResT>);
+
+      VERIFY(cv == res);
+      static_assert(std::same_as<decltype(cv), ResT>);
     };
 
-    if constexpr (OpId == 0)
-      check(x + cy, cx + y);
-    if constexpr (OpId == 1)
-      check(x - cy, cx - y);
-    if constexpr (OpId == 2)
-      check(x * cy, cx * y);
-    if constexpr (OpId == 3)
-      check(x / cy, cx / y);
-    if constexpr (OpId == 4)
-      check(x % cy, cx % y);
-    if constexpr (OpId == 5)
-      check(x << cy, cx << y);
-    if constexpr (OpId == 6)
-      check(x >> cy, cx >> y);
-    if constexpr (OpId == 7)
-      check(x & cy, cx & y);
-    if constexpr (OpId == 8)
-      check(x | cy, cx | y);
-    if constexpr (OpId == 10)
-      check(x && cy, cx && y);
-    if constexpr (OpId == 11)
-      check(x || cy, cx || y);
-    if constexpr (OpId == 12)
-      check(x <=> cy, cx <=> y);
-    if constexpr (OpId == 13)
-      check(x < cy, cx < y);
-    if constexpr (OpId == 14)
-      check(x <= cy, cx <= y);
-    if constexpr (OpId == 15)
-      check(x == cy, cx == y);
-    if constexpr (OpId == 16)
-      check(x != cy, cx != y);
-    if constexpr (OpId == 17)
-      check(x > cy, cx > y);
-    if constexpr (OpId == 18)
-      check(x >= cy, cx >= y);
-    if constexpr (OpId == 19)
-      check(x += cy, cx += y);
-    if constexpr (OpId == 20)
-      check(x -= cy, cx -= y);
-    if constexpr (OpId == 21)
-      check(x *= cy, cx *= y);
-    if constexpr (OpId == 22)
-      check(x /= cy, cx /= y);
-    if constexpr (OpId == 23)
-      check(x %= cy, cx %= y);
-    if constexpr (OpId == 24)
-      check(x &= cy, cx &= y);
-    if constexpr (OpId == 25)
-      check(x |= cy, cx |= y);
-    if constexpr (OpId == 26)
-      check(x ^= cy, cx ^= y);
-    if constexpr (OpId == 27)
-      check(x <<= cy, cx <<= y);
-    if constexpr (OpId == 28)
-      check(x >>= cy, cx >>= y);
+#define CHECK_OP(Id, Op)           \
+    if constexpr (OpId == Id)      \
+      check(x Op cy, cx Op y, Id); \
+    else                           \
+      static_assert(!requires { x Op cy; } && !requires { cx Op y; })
+
+#define CHECK_OP_F(Id, Op, Fb)          \
+    if constexpr (OpId == Fb)           \
+      check(x Op cy, cx Op y, Id Op 0); \
+    else CHECK_OP(Id, Op)
+
+    CHECK_OP( 0, +);
+    CHECK_OP( 1, -);
+    CHECK_OP( 2, *);
+    CHECK_OP( 3, /);
+    CHECK_OP( 4, %);
+    CHECK_OP( 5, <<);
+    CHECK_OP( 6, >>);
+    CHECK_OP( 7, &);
+    CHECK_OP( 8, |);
+    CHECK_OP( 9, ^);
+    CHECK_OP(10, &&);
+    CHECK_OP(11, ||);
+    CHECK_OP(12, <=>);
+    CHECK_OP_F(13, <,  12);
+    CHECK_OP_F(14, <=, 12);
+    CHECK_OP_F(17, >,  12);
+    CHECK_OP_F(18, >=, 12);
+    CHECK_OP(15, ==);
+    CHECK_OP(16, !=);
+    CHECK_OP(19, +=);
+    CHECK_OP(20, -=);
+    CHECK_OP(21, *=);
+    CHECK_OP(22, /=);
+    CHECK_OP(23, %=);
+    CHECK_OP(24, &=);
+    CHECK_OP(25, |=);
+    CHECK_OP(26, ^=);
+    CHECK_OP(27, <<=);
+    CHECK_OP(28, >>=);
+#undef CHECK_OP_F
+#undef CHECK_OP
+
     static_assert(n_binary_ops == 29);
   }
 
@@ -555,6 +695,7 @@ test_binary_operators_all()
     (test_binary_operator<free_ops::BinaryOps, Idx>(), ...);
     (test_mixed_binary_operators<free_ops::BinaryOps, Idx>(), ...);
     (test_binary_operator<member_ops::BinaryOps, Idx>(), ...);
+    (test_binary_operator<mutable_ops::BinaryOps, Idx, -1>(), ...);
   };
   run(std::make_index_sequence<n_binary_ops>());
 }