Skip to content

P2781R9 std::constant_wrapper #8018

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 16, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion source/containers.tex
Original file line number Diff line number Diff line change
Expand Up @@ -20190,7 +20190,7 @@

template<class T>
concept @\defexposconcept{integral-constant-like}@ = // \expos
is_integral_v<decltype(T::value)> &&
is_integral_v<remove_cv_ref_t<decltype(T::value)>> &&
!is_same_v<bool, remove_const_t<decltype(T::value)>> &&
@\libconcept{convertible_to}@<T, decltype(T::value)> &&
@\libconcept{equality_comparable_with}@<T, decltype(T::value)> &&
Expand Down
274 changes: 274 additions & 0 deletions source/meta.tex
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,22 @@
using @\libglobal{true_type}@ = bool_constant<true>;
using @\libglobal{false_type}@ = bool_constant<false>;

// \ref{const.wrap.class}, class template \tcode{constant_wrapper}
template<class T>
struct @\exposidnc{cw-fixed-value}@; // \expos

template<@\exposidnc{cw-fixed-value}@ X, class = typename decltype(@\exposid{cw-fixed-value}@(X))::@\exposid{type}@>
struct constant_wrapper;

template<class T>
concept @\defexposconceptnc{constexpr-param}@ = // \expos
requires { typename constant_wrapper<T::value>; };

struct @\exposidnc{cw-operators}@; // \expos

template<@\exposid{cw-fixed-value}@ X>
constexpr auto @\libglobal{cw}@ = constant_wrapper<X>{};

// \ref{meta.unary.cat}, primary type categories
template<class T> struct is_void;
template<class T> struct is_null_pointer;
Expand Down Expand Up @@ -631,6 +647,264 @@
are used as base classes to define
the interface for various type traits.

\rSec2[const.wrap.class]{Class template \tcode{constant_wrapper}}

\begin{codeblock}
template<class T>
struct @\exposidnc{cw-fixed-value}@ { // \expos
using @\exposidnc{type}@ = T; // \expos
constexpr @\exposidnc{cw-fixed-value}@(type v) noexcept : data(v) {}
T @\exposidnc{data}@; // \expos
};

template<class T, size_t Extent>
struct @\exposidnc{cw-fixed-value}@<T[Extent]> { // \expos
using @\exposidnc{type}@ = T[Extent]; // \expos
constexpr @\exposidnc{cw-fixed-value}@(T (&arr)[Extent]) noexcept;
T @\exposidnc{data}@[Extent]; // \expos
};

template<class T, size_t Extent>
@\exposidnc{cw-fixed-value}@(T (&)[Extent]) -> @\exposidnc{cw-fixed-value}@<T[Extent]>; // \expos

struct @\exposidnc{cw-operators}@ { // \expos
// unary operators
template<@\exposconcept{constexpr-param}@ T>
friend constexpr auto operator+(T) noexcept -> constant_wrapper<(+T::value)>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
friend constexpr auto operator+(T) noexcept -> constant_wrapper<(+T::value)>
friend constexpr auto @\libmember{operator+}{constant_wrapper}@(T) noexcept -> constant_wrapper<(+T::value)>

Same for all the other operators. Though, constant_wrapper isn't 100% correct, I believe it's what is most helpful even to implementers.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, this would seem to require a bunch of new escaping. Let's do that later.

{ return {}; }
template<@\exposconcept{constexpr-param}@ T>
friend constexpr auto operator-(T) noexcept -> constant_wrapper<(-T::value)>
{ return {}; }
template<@\exposconcept{constexpr-param}@ T>
friend constexpr auto operator~(T) noexcept -> constant_wrapper<(~T::value)>
{ return {}; }
template<@\exposconcept{constexpr-param}@ T>
friend constexpr auto operator!(T) noexcept -> constant_wrapper<(!T::value)>
{ return {}; }
template<@\exposconcept{constexpr-param}@ T>
friend constexpr auto operator&(T) noexcept -> constant_wrapper<(&T::value)>
{ return {}; }
template<@\exposconcept{constexpr-param}@ T>
friend constexpr auto operator*(T) noexcept -> constant_wrapper<(*T::value)>
{ return {}; }

// binary operators
template<@\exposconcept{constexpr-param}@ L, @\exposconcept{constexpr-param}@ R>
friend constexpr auto operator+(L, R) noexcept -> constant_wrapper<(L::value + R::value)>
{ return {}; }
template<@\exposconcept{constexpr-param}@ L, @\exposconcept{constexpr-param}@ R>
friend constexpr auto operator-(L, R) noexcept -> constant_wrapper<(L::value - R::value)>
{ return {}; }
template<@\exposconcept{constexpr-param}@ L, @\exposconcept{constexpr-param}@ R>
friend constexpr auto operator*(L, R) noexcept -> constant_wrapper<(L::value * R::value)>
{ return {}; }
template<@\exposconcept{constexpr-param}@ L, @\exposconcept{constexpr-param}@ R>
friend constexpr auto operator/(L, R) noexcept -> constant_wrapper<(L::value / R::value)>
{ return {}; }
template<@\exposconcept{constexpr-param}@ L, @\exposconcept{constexpr-param}@ R>
friend constexpr auto operator%(L, R) noexcept -> constant_wrapper<(L::value % R::value)>
{ return {}; }

template<@\exposconcept{constexpr-param}@ L, @\exposconcept{constexpr-param}@ R>
friend constexpr auto operator<<(L, R) noexcept -> constant_wrapper<(L::value << R::value)>
{ return {}; }
template<@\exposconcept{constexpr-param}@ L, @\exposconcept{constexpr-param}@ R>
friend constexpr auto operator>>(L, R) noexcept -> constant_wrapper<(L::value >> R::value)>
{ return {}; }
template<@\exposconcept{constexpr-param}@ L, @\exposconcept{constexpr-param}@ R>
friend constexpr auto operator&(L, R) noexcept -> constant_wrapper<(L::value & R::value)>
{ return {}; }
template<@\exposconcept{constexpr-param}@ L, @\exposconcept{constexpr-param}@ R>
friend constexpr auto operator|(L, R) noexcept -> constant_wrapper<(L::value | R::value)>
{ return {}; }
template<@\exposconcept{constexpr-param}@ L, @\exposconcept{constexpr-param}@ R>
friend constexpr auto operator^(L, R) noexcept -> constant_wrapper<(L::value ^ R::value)>
{ return {}; }

template<@\exposconcept{constexpr-param}@ L, @\exposconcept{constexpr-param}@ R>
requires (!is_constructible_v<bool, decltype(L::value)> ||
!is_constructible_v<bool, decltype(R::value)>)
friend constexpr auto operator&&(L, R) noexcept -> constant_wrapper<(L::value && R::value)>
{ return {}; }
template<@\exposconcept{constexpr-param}@ L, @\exposconcept{constexpr-param}@ R>
requires (!is_constructible_v<bool, decltype(L::value)> ||
!is_constructible_v<bool, decltype(R::value)>)
friend constexpr auto operator||(L, R) noexcept -> constant_wrapper<(L::value || R::value)>
{ return {}; }

// comparisons
template<@\exposconcept{constexpr-param}@ L, @\exposconcept{constexpr-param}@ R>
friend constexpr auto operator<=>(L, R) noexcept -> constant_wrapper<(L::value <=> R::value)>
{ return {}; }
template<@\exposconcept{constexpr-param}@ L, @\exposconcept{constexpr-param}@ R>
friend constexpr auto operator<(L, R) noexcept -> constant_wrapper<(L::value < R::value)>
{ return {}; }
template<@\exposconcept{constexpr-param}@ L, @\exposconcept{constexpr-param}@ R>
friend constexpr auto operator<=(L, R) noexcept -> constant_wrapper<(L::value <= R::value)>
{ return {}; }
template<@\exposconcept{constexpr-param}@ L, @\exposconcept{constexpr-param}@ R>
friend constexpr auto operator==(L, R) noexcept -> constant_wrapper<(L::value == R::value)>
{ return {}; }
template<@\exposconcept{constexpr-param}@ L, @\exposconcept{constexpr-param}@ R>
friend constexpr auto operator!=(L, R) noexcept -> constant_wrapper<(L::value != R::value)>
{ return {}; }
template<@\exposconcept{constexpr-param}@ L, @\exposconcept{constexpr-param}@ R>
friend constexpr auto operator>(L, R) noexcept -> constant_wrapper<(L::value > R::value)>
{ return {}; }
template<@\exposconcept{constexpr-param}@ L, @\exposconcept{constexpr-param}@ R>
friend constexpr auto operator>=(L, R) noexcept -> constant_wrapper<(L::value >= R::value)>
{ return {}; }

template<@\exposconcept{constexpr-param}@ L, @\exposconcept{constexpr-param}@ R>
friend constexpr auto operator,(L, R) noexcept = delete;
template<@\exposconcept{constexpr-param}@ L, @\exposconcept{constexpr-param}@ R>
friend constexpr auto operator->*(L, R) noexcept -> constant_wrapper<L::value->*(R::value)>
{ return {}; }

// call and index
template<@\exposconcept{constexpr-param}@ T, @\exposconcept{constexpr-param}@... Args>
constexpr auto operator()(this T, Args...) noexcept
requires requires(Args...) { constant_wrapper<T::value(Args::value...)>(); }
{ return constant_wrapper<T::value(Args::value...)>{}; }
template<@\exposconcept{constexpr-param}@ T, @\exposconcept{constexpr-param}@... Args>
constexpr auto operator[](this T, Args...) noexcept
-> constant_wrapper<(T::value[Args::value...])>
{ return {}; }

// pseudo-mutators
template<@\exposconcept{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<@\exposconcept{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<@\exposconcept{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<@\exposconcept{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<@\exposconcept{constexpr-param}@ T, @\exposconcept{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<@\exposconcept{constexpr-param}@ T, @\exposconcept{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<@\exposconcept{constexpr-param}@ T, @\exposconcept{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<@\exposconcept{constexpr-param}@ T, @\exposconcept{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<@\exposconcept{constexpr-param}@ T, @\exposconcept{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<@\exposconcept{constexpr-param}@ T, @\exposconcept{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<@\exposconcept{constexpr-param}@ T, @\exposconcept{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<@\exposconcept{constexpr-param}@ T, @\exposconcept{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<@\exposconcept{constexpr-param}@ T, @\exposconcept{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<@\exposconcept{constexpr-param}@ T, @\exposconcept{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<@\exposid{cw-fixed-value}@ X, class>
struct constant_wrapper : cw-operators {
static constexpr const auto & value = X.@\exposid{data}@;
using type = constant_wrapper;
using value_type = typename decltype(X)::@\exposid{type}@;

template<@\exposconcept{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; }()>{}; }

constexpr operator decltype(auto)() const noexcept { return value; }
};
\end{codeblock}

\pnum
The class template \tcode{constant_wrapper} aids in metaprogramming by ensuring
that the evaluation of expressions comprised entirely of \tcode{constant_wrapper}
are core constant expressions\iref{expr.const},
regardless of the context in which they appear.
In particular, this enables use of \tcode{constant_wrapper} values
that are passed as arguments to constexpr functions to be used in constant expressions.

\pnum
\begin{note}
The unnamed second template parameter to \tcode{constant_wrapper} is present
to aid argument-dependent lookup\iref{basic.lookup.argdep}
in finding overloads for which \tcode{constant_wrapper}'s wrapped value is a suitable argument,
but for which the \tcode{constant_wrapper} itself is not.
\end{note}

\pnum
\begin{example}
\begin{codeblock}
constexpr auto initial_phase(auto quantity_1, auto quantity_2) {
return quantity_1 + quantity_2;
}

constexpr auto middle_phase(auto tbd) {
return tbd;
}

void final_phase(auto gathered, auto available) {
if constexpr (gathered == available)
std::cout << "Profit!\n";
}

void impeccable_underground_planning() {
auto gathered_quantity = middle_phase(initial_phase(std::cw<42>, std::cw<13>));
static_assert(gathered_quantity == 55);
auto all_available = std::cw<55>;
final_phase(gathered_quantity, all_available);
}

void deeply_flawed_underground_planning() {
constexpr auto gathered_quantity = middle_phase(initial_phase(42, 13));
constexpr auto all_available = 55;
final_phase(gathered_quantity, all_available); // error: \tcode{gathered == available}
// is not a constant expression
}
\end{codeblock}
\end{example}

\begin{itemdecl}
constexpr @\exposid{cw-fixed-value}@(T (&arr)[Extent]) noexcept;
\end{itemdecl}

\begin{itemdescr}
\pnum
\effects
Initialize elements of \exposid{data} with corresponding elements of \tcode{arr}.
\end{itemdescr}

\rSec2[meta.unary]{Unary type traits}

\rSec3[meta.unary.general]{General}
Expand Down
1 change: 1 addition & 0 deletions source/support.tex
Original file line number Diff line number Diff line change
Expand Up @@ -618,6 +618,7 @@
#define @\defnlibxname{cpp_lib_complex_udls}@ 201309L // also in \libheader{complex}
#define @\defnlibxname{cpp_lib_concepts}@ 202207L
// freestanding, also in \libheader{concepts}, \libheader{compare}
#define @\defnlibxname{cpp_lib_constant_wrapper}@ 202506L // also in \libheader{type_traits}
#define @\defnlibxname{cpp_lib_constexpr_algorithms}@ 202306L // also in \libheader{algorithm}, \libheader{utility}
#define @\defnlibxname{cpp_lib_constexpr_atomic}@ 202411L // also in \libheader{atomic}
#define @\defnlibxname{cpp_lib_constexpr_bitset}@ 202207L // also in \libheader{bitset}
Expand Down