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

4383. constant_wrapper's pseudo-mutators are underconstrained

Section: 21.3.5 [const.wrap.class] Status: WP Submitter: Hewill Kang Opened: 2025-09-24 Last modified: 2025-11-11

Priority: 1

View all other issues in [const.wrap.class].

View all issues with WP status.

Discussion:

Unlike other operators, constant_wrapper's pseudo-mutators only require that the wrapped type has corresponding mutators, but do not require them to be constexpr or to return a sensible value. This inconsistency loses the SFINAE friendliness (demo):

#include <type_traits>

void test(auto t) {
  if constexpr (requires { +t; })  // ok
    +t;
  if constexpr (requires { -t; })  // ok
    -t;
  if constexpr (requires { ++t; }) // hard error
    ++t;
  if constexpr (requires { --t; }) // hard error
    --t;
}

struct S {
  /* constexpr */ int operator+() const { return 0; }
  /* constexpr */ int operator++() { return 0; }
  constexpr void operator-() const { }
  constexpr void operator--() { }
};

int main() {
  test(std::cw<S{}>);
}

Since these pseudo-mutators have constraints, it is reasonable to further require constant expressions.

[2025-10-17; Reflector poll.]

Set priority to 1 after reflector poll.

operator+= changed between P2781R4 and P2781R5, intent is unclear.

Previous resolution [SUPERSEDED]:

This wording is relative to N5014.

  1. Modify 21.3.5 [const.wrap.class], class template constant_wrapper synopsis, as indicated:

    [Drafting note: The requires clause follows the form of constant_wrapper's function call operator.]

    struct cw-operators {                                                           // exposition only
      […]
      // pseudo-mutators
      template<constexpr-param T>
        constexpr auto operator++(this T) noexcept
          requires requires(T::value_type x) { constant_wrapper<++x>(); }
            { return constant_wrapper<[] { auto c = T::value; return ++c; }()>{}; }
      template<constexpr-param T>
        constexpr auto operator++(this T, int) noexcept
          requires requires(T::value_type x) { constant_wrapper<x++>(); }
            { return constant_wrapper<[] { auto c = T::value; return c++; }()>{}; }
    
      template<constexpr-param T>
        constexpr auto operator--(this T) noexcept
          requires requires(T::value_type x) { constant_wrapper<--x>(); }
            { return constant_wrapper<[] { auto c = T::value; return --c; }()>{}; }
      template<constexpr-param T>
        constexpr auto operator--(this T, int) noexcept
          requires requires(T::value_type x) { constant_wrapper<x-->(); }
            { return constant_wrapper<[] { auto c = T::value; return c--; }()>{}; }
    
      template<constexpr-param T, constexpr-param R>
        constexpr auto operator+=(this T, R) noexcept
          requires requires(T::value_type x) { constant_wrapper<x += R::value>(); }
            { return constant_wrapper<[] { auto v = T::value; return v += R::value; }()>{}; }
      template<constexpr-param T, constexpr-param R>
        constexpr auto operator-=(this T, R) noexcept
          requires requires(T::value_type x) { constant_wrapper<x -= R::value>(); }
            { return constant_wrapper<[] { auto v = T::value; return v -= R::value; }()>{}; }
      template<constexpr-param T, constexpr-param R>
        constexpr auto operator*=(this T, R) noexcept
          requires requires(T::value_type x) { constant_wrapper<x *= R::value>(); }
            { return constant_wrapper<[] { auto v = T::value; return v *= R::value; }()>{}; }
      template<constexpr-param T, constexpr-param R>
        constexpr auto operator/=(this T, R) noexcept
          requires requires(T::value_type x) { constant_wrapper<x /= R::value>(); }
            { return constant_wrapper<[] { auto v = T::value; return v /= R::value; }()>{}; }
      template<constexpr-param T, constexpr-param R>
        constexpr auto operator%=(this T, R) noexcept
          requires requires(T::value_type x) { constant_wrapper<x %= R::value>(); }
            { return constant_wrapper<[] { auto v = T::value; return v %= R::value; }()>{}; }
      template<constexpr-param T, constexpr-param R>
        constexpr auto operator&=(this T, R) noexcept
          requires requires(T::value_type x) { constant_wrapper<x &= R::value>(); }
            { return constant_wrapper<[] { auto v = T::value; return v &= R::value; }()>{}; }
      template<constexpr-param T, constexpr-param R>
        constexpr auto operator|=(this T, R) noexcept
          requires requires(T::value_type x) { constant_wrapper<x |= R::value>(); }
            { return constant_wrapper<[] { auto v = T::value; return v |= R::value; }()>{}; }
      template<constexpr-param T, constexpr-param R>
        constexpr auto operator^=(this T, R) noexcept
          requires requires(T::value_type x) { constant_wrapper<x ^= R::value>(); }
            { return constant_wrapper<[] { auto v = T::value; return v ^= R::value; }()>{}; }
      template<constexpr-param T, constexpr-param R>
        constexpr auto operator<<=(this T, R) noexcept
          requires requires(T::value_type x) { constant_wrapper<x <<= R::value>(); }
            { return constant_wrapper<[] { auto v = T::value; return v <<= R::value; }()>{}; }
      template<constexpr-param T, constexpr-param R>
        constexpr auto operator>>=(this T, R) noexcept
          requires requires(T::value_type x) { constant_wrapper<x >>= R::value>(); }
            { return constant_wrapper<[] { auto v = T::value; return v >>= R::value; }()>{}; }
    };
    

[2025-11-05; Zach provides improved wording]

[Kona 2025-11-07; approved by LWG. Status changed: New → Immediate.]

[Kona 2025-11-08; Status changed: Immediate → WP.]

Proposed resolution:

This wording is relative to N5014.

  1. Modify 21.3.5 [const.wrap.class], class template constant_wrapper synopsis, as indicated:

    [Drafting note: The requires clause follows the form of constant_wrapper's function call operator.]

      struct cw-operators {                                                           // exposition only
        […]
        // pseudo-mutators
        template<constexpr-param T>
          constexpr auto operator++(this T) noexcept requires requires(T::value_type x) { ++x; }
            { return constant_wrapper<[] { auto c = T::value; return ++c; }()>{}; }
        template<constexpr-param T>
          constexpr auto operator++(this T, int) noexcept requires requires(T::value_type x) { x++; }
            { return constant_wrapper<[] { auto c = T::value; return c++; }()>{}; }
    
        template<constexpr-param T>
          constexpr auto operator--(this T) noexcept requires requires(T::value_type x) { --x; }
            { return constant_wrapper<[] { auto c = T::value; return --c; }()>{}; }
        template<constexpr-param T>
          constexpr auto operator--(this T, int) noexcept requires requires(T::value_type x) { x--; }
            { return constant_wrapper<[] { auto c = T::value; return c--; }()>{}; }
    
        template<constexpr-param T, constexpr-param R>
          constexpr auto operator+=(this T, R) noexcept requires requires(T::value_type x) { x += R::value; }
            { return constant_wrapper<[] { auto v = T::value; return v += R::value; }()>{}; }
        template<constexpr-param T, constexpr-param R>
          constexpr auto operator-=(this T, R) noexcept requires requires(T::value_type x) { x -= R::value; }
            { return constant_wrapper<[] { auto v = T::value; return v -= R::value; }()>{}; }
        template<constexpr-param T, constexpr-param R>
          constexpr auto operator*=(this T, R) noexcept requires requires(T::value_type x) { x *= R::value; }
            { return constant_wrapper<[] { auto v = T::value; return v *= R::value; }()>{}; }
        template<constexpr-param T, constexpr-param R>
          constexpr auto operator/=(this T, R) noexcept requires requires(T::value_type x) { x /= R::value; }
            { return constant_wrapper<[] { auto v = T::value; return v /= R::value; }()>{}; }
        template<constexpr-param T, constexpr-param R>
          constexpr auto operator%=(this T, R) noexcept requires requires(T::value_type x) { x %= R::value; }
            { return constant_wrapper<[] { auto v = T::value; return v %= R::value; }()>{}; }
        template<constexpr-param T, constexpr-param R>
          constexpr auto operator&=(this T, R) noexcept requires requires(T::value_type x) { x &= R::value; }
            { return constant_wrapper<[] { auto v = T::value; return v &= R::value; }()>{}; }
        template<constexpr-param T, constexpr-param R>
          constexpr auto operator|=(this T, R) noexcept requires requires(T::value_type x) { x |= R::value; }
            { return constant_wrapper<[] { auto v = T::value; return v |= R::value; }()>{}; }
        template<constexpr-param T, constexpr-param R>
          constexpr auto operator^=(this T, R) noexcept requires requires(T::value_type x) { x ^= R::value; }
            { return constant_wrapper<[] { auto v = T::value; return v ^= R::value; }()>{}; }
        template<constexpr-param T, constexpr-param R>
          constexpr auto operator<<=(this T, R) noexcept requires requires(T::value_type x) { x <<= R::value; }
            { return constant_wrapper<[] { auto v = T::value; return v <<= R::value; }()>{}; }
        template<constexpr-param T, constexpr-param R>
          constexpr auto operator>>=(this T, R) noexcept requires requires(T::value_type x) { x >>= R::value; }
            { return constant_wrapper<[] { auto v = T::value; return v >>= R::value; }()>{}; }
        template<constexpr-param T>
          constexpr auto operator++(this T) noexcept -> constant_wrapper<++Y> { return {}; }
        template<constexpr-param T>
          constexpr auto operator++(this T, int) noexcept -> constant_wrapper<Y++> { return {}; }
        template<constexpr-param T>
          constexpr auto operator--(this T) noexcept -> constant_wrapper<--Y> { return {}; }
        template<constexpr-param T>
          constexpr auto operator--(this T, int) noexcept -> constant_wrapper<Y--> { return {}; }
    
        template<constexpr-param T, constexpr-param R>
          constexpr auto operator+=(this T, R) noexcept -> constant_wrapper<(T::value += R::value)> { return {}; }
        template<constexpr-param T, constexpr-param R>
          constexpr auto operator-=(this T, R) noexcept -> constant_wrapper<(T::value -= R::value)> { return {}; }
        template<constexpr-param T, constexpr-param R>
          constexpr auto operator*=(this T, R) noexcept -> constant_wrapper<(T::value *= R::value)> { return {}; }
        template<constexpr-param T, constexpr-param R>
          constexpr auto operator/=(this T, R) noexcept -> constant_wrapper<(T::value /= R::value)> { return {}; }
        template<constexpr-param T, constexpr-param R>
          constexpr auto operator%=(this T, R) noexcept -> constant_wrapper<(T::value %= R::value)> { return {}; }
        template<constexpr-param T, constexpr-param R>
          constexpr auto operator&=(this T, R) noexcept -> constant_wrapper<(T::value &= R::value)> { return {}; }
        template<constexpr-param T, constexpr-param R>
          constexpr auto operator|=(this T, R) noexcept -> constant_wrapper<(T::value |= R::value)> { return {}; }
        template<constexpr-param T, constexpr-param R>
          constexpr auto operator^=(this T, R) noexcept -> constant_wrapper<(T::value ^= R::value)> { return {}; }
        template<constexpr-param T, constexpr-param R>
          constexpr auto operator<<=(this T, R) noexcept -> constant_wrapper<(T::value <<= R::value)> { return {}; }
        template<constexpr-param T, constexpr-param R>
          constexpr auto operator>>=(this T, R) noexcept -> constant_wrapper<(T::value >>= R::value)> { return {}; }
      };
    }
    
    template<cw-fixed-value X, typename>
    struct constant_wrapper: cw-operators {
      static constexpr const auto & value = X.data;
      using type = constant_wrapper;
      using value_type = typename decltype(X)::type;
    
      template<constexpr-param R>
        constexpr auto operator=(R) const noexcept requires requires(value_type x) { x = R::value; }
          { return constant_wrapper<[] { auto v = value; return v = R::value; }()>{}; }
      template<constexpr-param R>
        constexpr auto operator=(R) const noexcept -> constant_wrapper<X = R::value> { return {}; }
    
      constexpr operator decltype(auto)() const noexcept { return value; }
    };