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.
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-10-17
Priority: 1
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.
[2025-10-17; Reflector poll.]
Set priority to 1 after reflector poll.
operator+= changed between P2781R4 and P2781R5, intent is unclear.
Proposed resolution:
This wording is relative to N5014.
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; }()>{}; }
};