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

4383. constant_wrapper's pseudo-mutators are underconstrained

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

Priority: Not Prioritized

View all issues with New 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.

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) { 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; }()>{}; }
    };