[libcxx-commits] [libcxx] [libcxx] Implement `std::constant_wrapper` (PR #191695)

via libcxx-commits libcxx-commits at lists.llvm.org
Mon Apr 13 06:35:19 PDT 2026


https://github.com/huixie90 updated https://github.com/llvm/llvm-project/pull/191695

>From 69f8e47ec187a395d60348594dff201f80f4f900 Mon Sep 17 00:00:00 2001
From: Hui Xie <hui.xie1990 at gmail.com>
Date: Sun, 12 Apr 2026 09:35:44 +0100
Subject: [PATCH 1/3] [libcxx] Implement `std::constant_wrapper`

---
 libcxx/docs/FeatureTestMacroTable.rst         |   2 +
 libcxx/docs/ReleaseNotes/23.rst               |   2 +
 libcxx/docs/Status/Cxx2cIssues.csv            |   6 +-
 libcxx/docs/Status/Cxx2cPapers.csv            |   4 +-
 libcxx/include/CMakeLists.txt                 |   1 +
 libcxx/include/__utility/constant_wrapper.h   | 335 ++++++++++
 libcxx/include/module.modulemap.in            |   1 +
 libcxx/include/span                           |   2 +-
 libcxx/include/utility                        |  17 +
 libcxx/include/version                        |   2 +
 .../views.span/span.cons/deduct.pass.cpp      |   8 +
 .../utility.version.compile.pass.cpp          |  27 +
 .../version.version.compile.pass.cpp          |  27 +
 .../utilities/const.wrap.class/adl.pass.cpp   |  54 ++
 .../const.wrap.class/assign.pass.cpp          |  86 +++
 .../const.wrap.class/binary_ops.pass.cpp      | 590 ++++++++++++++++++
 .../utilities/const.wrap.class/call.pass.cpp  | 239 +++++++
 .../utilities/const.wrap.class/comma.pass.cpp |  66 ++
 .../utilities/const.wrap.class/comp.pass.cpp  | 424 +++++++++++++
 .../const.wrap.class/convert.pass.cpp         |  90 +++
 .../const.wrap.class/ctad.compile.pass.cpp    |  37 ++
 .../utilities/const.wrap.class/cw.pass.cpp    |  74 +++
 .../cw_fixed.array.ctor.pass.cpp              |  91 +++
 .../const.wrap.class/cw_fixed.ctor.pass.cpp   |  75 +++
 .../const.wrap.class/general.pass.cpp         |  47 ++
 .../std/utilities/const.wrap.class/helpers.h  |  23 +
 .../const.wrap.class/mem_ptr.pass.cpp         | 116 ++++
 .../const.wrap.class/pseudo_mutators.pass.cpp | 427 +++++++++++++
 .../const.wrap.class/subscript.pass.cpp       | 187 ++++++
 .../const.wrap.class/types.compile.pass.cpp   |  40 ++
 .../const.wrap.class/unary_ops.pass.cpp       | 249 ++++++++
 .../generate_feature_test_macro_components.py |   7 +
 32 files changed, 3350 insertions(+), 6 deletions(-)
 create mode 100644 libcxx/include/__utility/constant_wrapper.h
 create mode 100644 libcxx/test/std/utilities/const.wrap.class/adl.pass.cpp
 create mode 100644 libcxx/test/std/utilities/const.wrap.class/assign.pass.cpp
 create mode 100644 libcxx/test/std/utilities/const.wrap.class/binary_ops.pass.cpp
 create mode 100644 libcxx/test/std/utilities/const.wrap.class/call.pass.cpp
 create mode 100644 libcxx/test/std/utilities/const.wrap.class/comma.pass.cpp
 create mode 100644 libcxx/test/std/utilities/const.wrap.class/comp.pass.cpp
 create mode 100644 libcxx/test/std/utilities/const.wrap.class/convert.pass.cpp
 create mode 100644 libcxx/test/std/utilities/const.wrap.class/ctad.compile.pass.cpp
 create mode 100644 libcxx/test/std/utilities/const.wrap.class/cw.pass.cpp
 create mode 100644 libcxx/test/std/utilities/const.wrap.class/cw_fixed.array.ctor.pass.cpp
 create mode 100644 libcxx/test/std/utilities/const.wrap.class/cw_fixed.ctor.pass.cpp
 create mode 100644 libcxx/test/std/utilities/const.wrap.class/general.pass.cpp
 create mode 100644 libcxx/test/std/utilities/const.wrap.class/helpers.h
 create mode 100644 libcxx/test/std/utilities/const.wrap.class/mem_ptr.pass.cpp
 create mode 100644 libcxx/test/std/utilities/const.wrap.class/pseudo_mutators.pass.cpp
 create mode 100644 libcxx/test/std/utilities/const.wrap.class/subscript.pass.cpp
 create mode 100644 libcxx/test/std/utilities/const.wrap.class/types.compile.pass.cpp
 create mode 100644 libcxx/test/std/utilities/const.wrap.class/unary_ops.pass.cpp

diff --git a/libcxx/docs/FeatureTestMacroTable.rst b/libcxx/docs/FeatureTestMacroTable.rst
index 94342eb4a4f0a..dcdf385ace6f6 100644
--- a/libcxx/docs/FeatureTestMacroTable.rst
+++ b/libcxx/docs/FeatureTestMacroTable.rst
@@ -426,6 +426,8 @@ Status
     ---------------------------------------------------------- -----------------
     ``__cpp_lib_bitset``                                       ``202306L``
     ---------------------------------------------------------- -----------------
+    ``__cpp_lib_constant_wrapper``                             ``202603L``
+    ---------------------------------------------------------- -----------------
     ``__cpp_lib_constexpr_algorithms``                         ``202306L``
     ---------------------------------------------------------- -----------------
     ``__cpp_lib_constexpr_flat_map``                           ``202502L``
diff --git a/libcxx/docs/ReleaseNotes/23.rst b/libcxx/docs/ReleaseNotes/23.rst
index c21b378f7db4b..e79c01e937836 100644
--- a/libcxx/docs/ReleaseNotes/23.rst
+++ b/libcxx/docs/ReleaseNotes/23.rst
@@ -42,6 +42,8 @@ Implemented Papers
 - P3936R1: Safer ``atomic_ref::address`` (`Github <https://llvm.org/PR189594>`__)
 - P3953R3: Rename ``std::runtime_format`` (`Github <https://llvm.org/PR189624>`__)
 - P4052R0: Renaming saturation arithmetic functions (`Github <https://llvm.org/PR189589>`__)
+- P2781R9: ``std::constant_wrapper`` (`Github <https://llvm.org/PR148179>`__)
+- P3978R3: ``constant_wrapper`` should unwrap on call and subscript (`Github <https://llvm.org/PR189605>`__)
 
 Improvements and New Features
 -----------------------------
diff --git a/libcxx/docs/Status/Cxx2cIssues.csv b/libcxx/docs/Status/Cxx2cIssues.csv
index a4afad4efa884..3f707da64f919 100644
--- a/libcxx/docs/Status/Cxx2cIssues.csv
+++ b/libcxx/docs/Status/Cxx2cIssues.csv
@@ -209,7 +209,7 @@
 "`LWG4376 <https://wg21.link/LWG4376>`__","ABI tag in return type of [simd.mask.unary] is overconstrained","2025-11 (Kona)","","","`#171367 <https://github.com/llvm/llvm-project/issues/171367>`__",""
 "`LWG4377 <https://wg21.link/LWG4377>`__","Misleading note about lock-free property of ``std::atomic_ref``","2025-11 (Kona)","","","`#171368 <https://github.com/llvm/llvm-project/issues/171368>`__",""
 "`LWG4382 <https://wg21.link/LWG4382>`__","The ``simd::basic_mask(bool)`` overload needs to be more constrained","2025-11 (Kona)","","","`#171369 <https://github.com/llvm/llvm-project/issues/171369>`__",""
-"`LWG4383 <https://wg21.link/LWG4383>`__","``constant_wrapper``\'s pseudo-mutators are underconstrained","2025-11 (Kona)","","","`#171370 <https://github.com/llvm/llvm-project/issues/171370>`__",""
+"`LWG4383 <https://wg21.link/LWG4383>`__","``constant_wrapper``\'s pseudo-mutators are underconstrained","2025-11 (Kona)","|Complete|","23","`#171370 <https://github.com/llvm/llvm-project/issues/171370>`__",""
 "`LWG4384 <https://wg21.link/LWG4384>`__","``flat_set::erase(iterator)`` is underconstrained","2025-11 (Kona)","","","`#171371 <https://github.com/llvm/llvm-project/issues/171371>`__",""
 "`LWG4388 <https://wg21.link/LWG4388>`__","Align new definition of ``va_start`` with C23","2025-11 (Kona)","","","`#171372 <https://github.com/llvm/llvm-project/issues/171372>`__",""
 "`LWG4396 <https://wg21.link/LWG4396>`__","Improve ``inplace_vector(from_range_t, R&& rg)``","2025-11 (Kona)","","","`#171373 <https://github.com/llvm/llvm-project/issues/171373>`__",""
@@ -311,7 +311,7 @@
 "`LWG4496 <https://wg21.link/LWG4496>`__","Precedes vs Reachable in [meta.reflection]","2026-03 (Croydon)","","","`#189853 <https://github.com/llvm/llvm-project/issues/189853>`__",""
 "`LWG4497 <https://wg21.link/LWG4497>`__","``std::nullopt_t`` should be comparable","2026-03 (Croydon)","","","`#189854 <https://github.com/llvm/llvm-project/issues/189854>`__",""
 "`LWG4499 <https://wg21.link/LWG4499>`__","``flat_set::insert_range`` specification may be problematic","2026-03 (Croydon)","","","`#189855 <https://github.com/llvm/llvm-project/issues/189855>`__",""
-"`LWG4500 <https://wg21.link/LWG4500>`__","``constant_wrapper`` wording problems","2026-03 (Croydon)","","","`#189856 <https://github.com/llvm/llvm-project/issues/189856>`__",""
+"`LWG4500 <https://wg21.link/LWG4500>`__","``constant_wrapper`` wording problems","2026-03 (Croydon)","|Complete|","23","`#189856 <https://github.com/llvm/llvm-project/issues/189856>`__",""
 "`LWG4504 <https://wg21.link/LWG4504>`__","Wording problem in ``{simple_}counting_scope``","2026-03 (Croydon)","","","`#189857 <https://github.com/llvm/llvm-project/issues/189857>`__",""
 "`LWG4506 <https://wg21.link/LWG4506>`__","``source_location`` is explicitly unspecified if is constexpr or not","2026-03 (Croydon)","","","`#189858 <https://github.com/llvm/llvm-project/issues/189858>`__",""
 "`LWG4510 <https://wg21.link/LWG4510>`__","Ambiguity of ``std::ranges::advance`` and ``std::ranges::next`` when the difference type is also a sentinel type","2026-03 (Croydon)","","","`#189859 <https://github.com/llvm/llvm-project/issues/189859>`__",""
@@ -320,7 +320,7 @@
 "`LWG4514 <https://wg21.link/LWG4514>`__","Missing absolute value of ``init`` in ``vector_two_norm`` and ``matrix_frob_norm``","2026-03 (Croydon)","","","`#189862 <https://github.com/llvm/llvm-project/issues/189862>`__",""
 "`LWG4517 <https://wg21.link/LWG4517>`__","``data_member_spec`` should throw for *cv*-qualified unnamed bit-fields","2026-03 (Croydon)","","","`#189863 <https://github.com/llvm/llvm-project/issues/189863>`__",""
 "`LWG4522 <https://wg21.link/LWG4522>`__","Clarify that ``std::format`` transcodes for ``std::wformat_string``\s","2026-03 (Croydon)","","","`#189864 <https://github.com/llvm/llvm-project/issues/189864>`__",""
-"`LWG4523 <https://wg21.link/LWG4523>`__","``constant_wrapper`` should assign to value","2026-03 (Croydon)","","","`#189865 <https://github.com/llvm/llvm-project/issues/189865>`__",""
+"`LWG4523 <https://wg21.link/LWG4523>`__","``constant_wrapper`` should assign to value","2026-03 (Croydon)","|Complete|","23","`#189865 <https://github.com/llvm/llvm-project/issues/189865>`__",""
 "`LWG4525 <https://wg21.link/LWG4525>`__","``task``'s ``final_suspend`` should move the result","2026-03 (Croydon)","","","`#189866 <https://github.com/llvm/llvm-project/issues/189866>`__",""
 "`LWG4527 <https://wg21.link/LWG4527>`__","``await_transform`` needs to use ``as_awaitable``","2026-03 (Croydon)","","","`#189867 <https://github.com/llvm/llvm-project/issues/189867>`__",""
 "`LWG4528 <https://wg21.link/LWG4528>`__","``task`` needs ``get_completion_signatures()``","2026-03 (Croydon)","","","`#189868 <https://github.com/llvm/llvm-project/issues/189868>`__",""
diff --git a/libcxx/docs/Status/Cxx2cPapers.csv b/libcxx/docs/Status/Cxx2cPapers.csv
index 7adf5a2122e62..6b706ab036e01 100644
--- a/libcxx/docs/Status/Cxx2cPapers.csv
+++ b/libcxx/docs/Status/Cxx2cPapers.csv
@@ -152,7 +152,7 @@
 "`P3060R3 <https://wg21.link/P3060R3>`__","Add ``std::views::indices(n)``","2025-06 (Sofia)","|Complete|","22","`#148175 <https://github.com/llvm/llvm-project/issues/148175>`__",""
 "`P2319R5 <https://wg21.link/P2319R5>`__","Prevent ``path`` presentation problems","2025-06 (Sofia)","","","`#148177 <https://github.com/llvm/llvm-project/issues/148177>`__",""
 "`P3223R2 <https://wg21.link/P3223R2>`__","Making ``std::istream::ignore`` less surprising","2025-06 (Sofia)","|Complete|","22","`#148178 <https://github.com/llvm/llvm-project/issues/148178>`__",""
-"`P2781R9 <https://wg21.link/P2781R9>`__","``std::constant_wrapper``","2025-06 (Sofia)","","","`#148179 <https://github.com/llvm/llvm-project/issues/148179>`__",""
+"`P2781R9 <https://wg21.link/P2781R9>`__","``std::constant_wrapper``","2025-06 (Sofia)","|Complete|","23","`#148179 <https://github.com/llvm/llvm-project/issues/148179>`__",""
 "`P3697R1 <https://wg21.link/P3697R1>`__","Minor additions to C++26 standard library hardening","2025-06 (Sofia)","","","`#148180 <https://github.com/llvm/llvm-project/issues/148180>`__",""
 "`P3552R3 <https://wg21.link/P3552R3>`__","Add a Coroutine Task Type","2025-06 (Sofia)","","","`#148182 <https://github.com/llvm/llvm-project/issues/148182>`__",""
 "`P1317R2 <https://wg21.link/P1317R2>`__","Remove return type deduction in ``std::apply``","2025-06 (Sofia)","","","`#148183 <https://github.com/llvm/llvm-project/issues/148183>`__",""
@@ -193,7 +193,7 @@
 "`P3828R1 <https://wg21.link/P3828R1>`__","Rename the to_input view to as_input","2026-03 (Croydon)","","","`#189602 <https://github.com/llvm/llvm-project/issues/189602>`__",""
 "`P3795R2 <https://wg21.link/P3795R2>`__","Miscellaneous Reflection Cleanup","2026-03 (Croydon)","","","`#189603 <https://github.com/llvm/llvm-project/issues/189603>`__",""
 "`P3948R1 <https://wg21.link/P3948R1>`__","``constant_wrapper`` is the only tool needed for passing constant expressions via function arguments","2026-03 (Croydon)","","","`#189604 <https://github.com/llvm/llvm-project/issues/189604>`__",""
-"`P3978R3 <https://wg21.link/P3978R3>`__","``constant_wrapper`` should unwrap on call and subscript","2026-03 (Croydon)","","","`#189605 <https://github.com/llvm/llvm-project/issues/189605>`__",""
+"`P3978R3 <https://wg21.link/P3978R3>`__","``constant_wrapper`` should unwrap on call and subscript","2026-03 (Croydon)","|Complete|","23","`#189605 <https://github.com/llvm/llvm-project/issues/189605>`__",""
 "`P3961R1 <https://wg21.link/P3961R1>`__","Less double indirection in ``function_ref`` (RU-220)","2026-03 (Croydon)","","","`#189606 <https://github.com/llvm/llvm-project/issues/189606>`__",""
 "`P3981R2 <https://wg21.link/P3981R2>`__","Better return types in ``std::inplace_vector`` and ``std::exception_ptr_cast``","2026-03 (Croydon)","","","`#189607 <https://github.com/llvm/llvm-project/issues/189607>`__",""
 "`P4022R0 <https://wg21.link/P4022R0>`__","Remove ``try_append_range`` from ``inplace_vector`` for now","2026-03 (Croydon)","","","`#189608 <https://github.com/llvm/llvm-project/issues/189608>`__",""
diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt
index 53165f0336b2d..712ad8bf66593 100644
--- a/libcxx/include/CMakeLists.txt
+++ b/libcxx/include/CMakeLists.txt
@@ -926,6 +926,7 @@ set(files
   __utility/as_lvalue.h
   __utility/auto_cast.h
   __utility/cmp.h
+  __utility/constant_wrapper.h
   __utility/convert_to_integral.h
   __utility/declval.h
   __utility/default_three_way_comparator.h
diff --git a/libcxx/include/__utility/constant_wrapper.h b/libcxx/include/__utility/constant_wrapper.h
new file mode 100644
index 0000000000000..440405fca304e
--- /dev/null
+++ b/libcxx/include/__utility/constant_wrapper.h
@@ -0,0 +1,335 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef _LIBCPP___UTILITY_CONSTANT_WRAPPER_H
+#define _LIBCPP___UTILITY_CONSTANT_WRAPPER_H
+
+#include <__config>
+#include <__cstddef/size_t.h>
+#include <__functional/invoke.h>
+#include <__type_traits/is_constructible.h>
+#include <__type_traits/remove_cvref.h>
+#include <__utility/forward.h>
+#include <__utility/integer_sequence.h>
+
+#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
+#  pragma GCC system_header
+#endif
+
+_LIBCPP_BEGIN_NAMESPACE_STD
+
+#if _LIBCPP_STD_VER >= 26
+
+template <class _Tp>
+struct __cw_fixed_value;
+
+template <__cw_fixed_value _Xp, class = typename decltype(_Xp)::__type>
+struct constant_wrapper;
+
+template <class _Tp>
+concept __constexpr_param = requires { typename constant_wrapper<_Tp::value>; };
+
+struct __cw_operators;
+
+template <__cw_fixed_value _Xp>
+constexpr auto cw = constant_wrapper<_Xp>{};
+
+template <class _Tp>
+struct __cw_fixed_value {
+  using __type _LIBCPP_NODEBUG = _Tp;
+  _LIBCPP_HIDE_FROM_ABI constexpr __cw_fixed_value(__type __v) noexcept : __data(__v) {}
+  _Tp __data;
+};
+
+template <class _Tp, size_t _Extent>
+struct __cw_fixed_value<_Tp[_Extent]> {
+  using __type _LIBCPP_NODEBUG = _Tp[_Extent];
+  _Tp __data[_Extent];
+
+  _LIBCPP_HIDE_FROM_ABI constexpr __cw_fixed_value(_Tp (&__arr)[_Extent]) noexcept
+      : __cw_fixed_value(__arr, make_index_sequence<_Extent>{}) {}
+
+private:
+  template <size_t... _Idxs>
+  _LIBCPP_HIDE_FROM_ABI constexpr __cw_fixed_value(_Tp (&__arr)[_Extent], index_sequence<_Idxs...>) noexcept
+      : __data{__arr[_Idxs]...} {}
+};
+
+template <class _Tp, size_t _Extent>
+__cw_fixed_value(_Tp (&)[_Extent]) -> __cw_fixed_value<_Tp[_Extent]>;
+
+struct __cw_operators {
+  // unary operators
+  template <__constexpr_param _Tp>
+  _LIBCPP_HIDE_FROM_ABI friend constexpr auto operator+(_Tp) noexcept -> constant_wrapper<(+_Tp::value)> {
+    return {};
+  }
+  template <__constexpr_param _Tp>
+  _LIBCPP_HIDE_FROM_ABI friend constexpr auto operator-(_Tp) noexcept -> constant_wrapper<(-_Tp::value)> {
+    return {};
+  }
+  template <__constexpr_param _Tp>
+  _LIBCPP_HIDE_FROM_ABI friend constexpr auto operator~(_Tp) noexcept -> constant_wrapper<(~_Tp::value)> {
+    return {};
+  }
+  template <__constexpr_param _Tp>
+  _LIBCPP_HIDE_FROM_ABI friend constexpr auto operator!(_Tp) noexcept -> constant_wrapper<(!_Tp::value)> {
+    return {};
+  }
+  template <__constexpr_param _Tp>
+  _LIBCPP_HIDE_FROM_ABI friend constexpr auto operator&(_Tp) noexcept -> constant_wrapper<(&_Tp::value)> {
+    return {};
+  }
+  template <__constexpr_param _Tp>
+  _LIBCPP_HIDE_FROM_ABI friend constexpr auto operator*(_Tp) noexcept -> constant_wrapper<(*_Tp::value)> {
+    return {};
+  }
+
+  // binary operators
+  template <__constexpr_param _Lp, __constexpr_param _Rp>
+  _LIBCPP_HIDE_FROM_ABI friend constexpr auto operator+(_Lp, _Rp) noexcept
+      -> constant_wrapper<(_Lp::value + _Rp::value)> {
+    return {};
+  }
+  template <__constexpr_param _Lp, __constexpr_param _Rp>
+  _LIBCPP_HIDE_FROM_ABI friend constexpr auto operator-(_Lp, _Rp) noexcept
+      -> constant_wrapper<(_Lp::value - _Rp::value)> {
+    return {};
+  }
+  template <__constexpr_param _Lp, __constexpr_param _Rp>
+  _LIBCPP_HIDE_FROM_ABI friend constexpr auto operator*(_Lp, _Rp) noexcept
+      -> constant_wrapper<(_Lp::value * _Rp::value)> {
+    return {};
+  }
+  template <__constexpr_param _Lp, __constexpr_param _Rp>
+  _LIBCPP_HIDE_FROM_ABI friend constexpr auto operator/(_Lp, _Rp) noexcept
+      -> constant_wrapper<(_Lp::value / _Rp::value)> {
+    return {};
+  }
+  template <__constexpr_param _Lp, __constexpr_param _Rp>
+  _LIBCPP_HIDE_FROM_ABI friend constexpr auto operator%(_Lp, _Rp) noexcept
+      -> constant_wrapper<(_Lp::value % _Rp::value)> {
+    return {};
+  }
+
+  template <__constexpr_param _Lp, __constexpr_param _Rp>
+  _LIBCPP_HIDE_FROM_ABI friend constexpr auto operator<<(_Lp, _Rp) noexcept
+      -> constant_wrapper<(_Lp::value << _Rp::value)> {
+    return {};
+  }
+  template <__constexpr_param _Lp, __constexpr_param _Rp>
+  _LIBCPP_HIDE_FROM_ABI friend constexpr auto operator>>(_Lp, _Rp) noexcept
+      -> constant_wrapper<(_Lp::value >> _Rp::value)> {
+    return {};
+  }
+  template <__constexpr_param _Lp, __constexpr_param _Rp>
+  _LIBCPP_HIDE_FROM_ABI friend constexpr auto operator&(_Lp, _Rp) noexcept
+      -> constant_wrapper<(_Lp::value & _Rp::value)> {
+    return {};
+  }
+  template <__constexpr_param _Lp, __constexpr_param _Rp>
+  _LIBCPP_HIDE_FROM_ABI friend constexpr auto operator|(_Lp, _Rp) noexcept
+      -> constant_wrapper<(_Lp::value | _Rp::value)> {
+    return {};
+  }
+  template <__constexpr_param _Lp, __constexpr_param _Rp>
+  _LIBCPP_HIDE_FROM_ABI friend constexpr auto operator^(_Lp, _Rp) noexcept
+      -> constant_wrapper<(_Lp::value ^ _Rp::value)> {
+    return {};
+  }
+
+  template <__constexpr_param _Lp, __constexpr_param _Rp>
+    requires(!is_constructible_v<bool, decltype(_Lp::value)> || !is_constructible_v<bool, decltype(_Rp::value)>)
+  _LIBCPP_HIDE_FROM_ABI friend constexpr auto operator&&(_Lp, _Rp) noexcept
+      -> constant_wrapper<(_Lp::value && _Rp::value)> {
+    return {};
+  }
+  template <__constexpr_param _Lp, __constexpr_param _Rp>
+    requires(!is_constructible_v<bool, decltype(_Lp::value)> || !is_constructible_v<bool, decltype(_Rp::value)>)
+  _LIBCPP_HIDE_FROM_ABI friend constexpr auto operator||(_Lp, _Rp) noexcept
+      -> constant_wrapper<(_Lp::value || _Rp::value)> {
+    return {};
+  }
+
+  // comparisons
+  template <__constexpr_param _Lp, __constexpr_param _Rp>
+  _LIBCPP_HIDE_FROM_ABI friend constexpr auto operator<=>(_Lp, _Rp) noexcept
+      -> constant_wrapper<(_Lp::value <=> _Rp::value)> {
+    return {};
+  }
+  template <__constexpr_param _Lp, __constexpr_param _Rp>
+  _LIBCPP_HIDE_FROM_ABI friend constexpr auto operator<(_Lp, _Rp) noexcept
+      -> constant_wrapper<(_Lp::value < _Rp::value)> {
+    return {};
+  }
+  template <__constexpr_param _Lp, __constexpr_param _Rp>
+  _LIBCPP_HIDE_FROM_ABI friend constexpr auto operator<=(_Lp, _Rp) noexcept
+      -> constant_wrapper<(_Lp::value <= _Rp::value)> {
+    return {};
+  }
+  template <__constexpr_param _Lp, __constexpr_param _Rp>
+  friend constexpr auto operator==(_Lp, _Rp) noexcept -> constant_wrapper<(_Lp::value == _Rp::value)> {
+    return {};
+  }
+  template <__constexpr_param _Lp, __constexpr_param _Rp>
+  _LIBCPP_HIDE_FROM_ABI friend constexpr auto operator!=(_Lp, _Rp) noexcept
+      -> constant_wrapper<(_Lp::value != _Rp::value)> {
+    return {};
+  }
+  template <__constexpr_param _Lp, __constexpr_param _Rp>
+  _LIBCPP_HIDE_FROM_ABI friend constexpr auto operator>(_Lp, _Rp) noexcept
+      -> constant_wrapper<(_Lp::value > _Rp::value)> {
+    return {};
+  }
+  template <__constexpr_param _Lp, __constexpr_param _Rp>
+  _LIBCPP_HIDE_FROM_ABI friend constexpr auto operator>=(_Lp, _Rp) noexcept
+      -> constant_wrapper<(_Lp::value >= _Rp::value)> {
+    return {};
+  }
+
+  template <__constexpr_param _Lp, __constexpr_param _Rp>
+  _LIBCPP_HIDE_FROM_ABI friend constexpr auto operator,(_Lp, _Rp) noexcept = delete;
+  template <__constexpr_param _Lp, __constexpr_param _Rp>
+  _LIBCPP_HIDE_FROM_ABI friend constexpr auto operator->*(_Lp, _Rp) noexcept
+      -> constant_wrapper<_Lp::value->*(_Rp::value)> {
+    return {};
+  }
+
+  // pseudo-mutators
+  template <__constexpr_param _Tp>
+  _LIBCPP_HIDE_FROM_ABI constexpr auto operator++(this _Tp) noexcept -> constant_wrapper<++(_Tp::value)> {
+    return {};
+  }
+  template <__constexpr_param _Tp>
+  _LIBCPP_HIDE_FROM_ABI constexpr auto operator++(this _Tp, int) noexcept -> constant_wrapper<(_Tp::value++)> {
+    return {};
+  }
+  template <__constexpr_param _Tp>
+  _LIBCPP_HIDE_FROM_ABI constexpr auto operator--(this _Tp) noexcept -> constant_wrapper<--(_Tp::value)> {
+    return {};
+  }
+  template <__constexpr_param _Tp>
+  _LIBCPP_HIDE_FROM_ABI constexpr auto operator--(this _Tp, int) noexcept -> constant_wrapper<(_Tp::value--)> {
+    return {};
+  }
+
+  template <__constexpr_param _Tp, __constexpr_param _Rp>
+  _LIBCPP_HIDE_FROM_ABI constexpr auto operator+=(this _Tp, _Rp) noexcept
+      -> constant_wrapper<(_Tp::value += _Rp::value)> {
+    return {};
+  }
+  template <__constexpr_param _Tp, __constexpr_param _Rp>
+  _LIBCPP_HIDE_FROM_ABI constexpr auto operator-=(this _Tp, _Rp) noexcept
+      -> constant_wrapper<(_Tp::value -= _Rp::value)> {
+    return {};
+  }
+  template <__constexpr_param _Tp, __constexpr_param _Rp>
+  _LIBCPP_HIDE_FROM_ABI constexpr auto operator*=(this _Tp, _Rp) noexcept
+      -> constant_wrapper<(_Tp::value *= _Rp::value)> {
+    return {};
+  }
+  template <__constexpr_param _Tp, __constexpr_param _Rp>
+  _LIBCPP_HIDE_FROM_ABI constexpr auto operator/=(this _Tp, _Rp) noexcept
+      -> constant_wrapper<(_Tp::value /= _Rp::value)> {
+    return {};
+  }
+  template <__constexpr_param _Tp, __constexpr_param _Rp>
+  _LIBCPP_HIDE_FROM_ABI constexpr auto operator%=(this _Tp, _Rp) noexcept
+      -> constant_wrapper<(_Tp::value %= _Rp::value)> {
+    return {};
+  }
+  template <__constexpr_param _Tp, __constexpr_param _Rp>
+  _LIBCPP_HIDE_FROM_ABI constexpr auto operator&=(this _Tp, _Rp) noexcept
+      -> constant_wrapper<(_Tp::value &= _Rp::value)> {
+    return {};
+  }
+  template <__constexpr_param _Tp, __constexpr_param _Rp>
+  _LIBCPP_HIDE_FROM_ABI constexpr auto operator|=(this _Tp, _Rp) noexcept
+      -> constant_wrapper<(_Tp::value |= _Rp::value)> {
+    return {};
+  }
+  template <__constexpr_param _Tp, __constexpr_param _Rp>
+  _LIBCPP_HIDE_FROM_ABI constexpr auto operator^=(this _Tp, _Rp) noexcept
+      -> constant_wrapper<(_Tp::value ^= _Rp::value)> {
+    return {};
+  }
+  template <__constexpr_param _Tp, __constexpr_param _Rp>
+  _LIBCPP_HIDE_FROM_ABI constexpr auto operator<<=(this _Tp, _Rp) noexcept
+      -> constant_wrapper<(_Tp::value <<= _Rp::value)> {
+    return {};
+  }
+  template <__constexpr_param _Tp, __constexpr_param _Rp>
+  _LIBCPP_HIDE_FROM_ABI constexpr auto operator>>=(this _Tp, _Rp) noexcept
+      -> constant_wrapper<(_Tp::value >>= _Rp::value)> {
+    return {};
+  }
+};
+
+template <__cw_fixed_value _Xp, class>
+struct constant_wrapper : __cw_operators {
+  static constexpr const auto& value = _Xp.__data;
+  using type                         = constant_wrapper;
+  using value_type                   = decltype(_Xp)::__type;
+
+private:
+  template <class... _Args>
+    requires(__constexpr_param<remove_cvref_t<_Args>> && ...)
+  _LIBCPP_HIDE_FROM_ABI static constexpr auto __call(_Args&&... __args) noexcept
+      -> constant_wrapper<std::invoke(value, remove_cvref_t<_Args>::value...)> {
+    return {};
+  }
+
+  template <class... _Args>
+  _LIBCPP_HIDE_FROM_ABI static constexpr auto
+  __call(_Args&&... __args) noexcept(noexcept(std::invoke(value, std::forward<_Args>(__args)...)))
+      -> decltype(std::invoke(value, std::forward<_Args>(__args)...)) {
+    return std::invoke(value, std::forward<_Args>(__args)...);
+  }
+
+  template <class... _Args>
+    requires(__constexpr_param<remove_cvref_t<_Args>> && ...)
+  _LIBCPP_HIDE_FROM_ABI static constexpr auto __subscript(_Args&&... __args) noexcept
+      -> constant_wrapper<value[remove_cvref_t<_Args>::value...]> {
+    return {};
+  }
+
+  template <class... _Args>
+  _LIBCPP_HIDE_FROM_ABI static constexpr auto
+  __subscript(_Args&&... __args) noexcept(noexcept(value[std::forward<_Args>(__args)...]))
+      -> decltype(value[std::forward<_Args>(__args)...]) {
+    return value[std::forward<_Args>(__args)...];
+  }
+
+public:
+  template <__constexpr_param _Rp>
+  _LIBCPP_HIDE_FROM_ABI constexpr auto operator=(_Rp) const noexcept -> constant_wrapper<(value = _Rp::value)> {
+    return {};
+  }
+
+  _LIBCPP_HIDE_FROM_ABI constexpr operator decltype(auto)() const noexcept { return value; }
+
+  template <class... _Args>
+  _LIBCPP_HIDE_FROM_ABI static constexpr auto
+  operator()(_Args&&... __args) noexcept(noexcept(constant_wrapper::__call(std::forward<_Args>(__args)...)))
+      -> decltype(constant_wrapper::__call(std::forward<_Args>(__args)...)) {
+    return __call(std::forward<_Args>(__args)...);
+  }
+
+  template <class... _Args>
+  _LIBCPP_HIDE_FROM_ABI static constexpr auto
+  operator[](_Args&&... __args) noexcept(noexcept(constant_wrapper::__subscript(std::forward<_Args>(__args)...)))
+      -> decltype(constant_wrapper::__subscript(std::forward<_Args>(__args)...)) {
+    return __subscript(std::forward<_Args>(__args)...);
+  }
+};
+
+#endif // _LIBCPP_STD_VER >= 26
+
+_LIBCPP_END_NAMESPACE_STD
+
+#endif // _LIBCPP___UTILITY_CONSTANT_WRAPPER_H
diff --git a/libcxx/include/module.modulemap.in b/libcxx/include/module.modulemap.in
index d6e8289b7c8b0..5748cc6f218a8 100644
--- a/libcxx/include/module.modulemap.in
+++ b/libcxx/include/module.modulemap.in
@@ -2179,6 +2179,7 @@ module std [system] {
       export std_core.type_traits.decay // the macro expansion uses that trait
     }
     module cmp                             { header "__utility/cmp.h" }
+    module constant_wrapper                { header "__utility/constant_wrapper.h" }
     module convert_to_integral             { header "__utility/convert_to_integral.h" }
     module default_three_way_comparator    { header "__utility/default_three_way_comparator.h" }
     module element_count                   { header "__utility/element_count.h" }
diff --git a/libcxx/include/span b/libcxx/include/span
index 1911badd88cb1..b5d7844b0b7ee 100644
--- a/libcxx/include/span
+++ b/libcxx/include/span
@@ -614,7 +614,7 @@ template <class _Tp, size_t _Extent>
 #    if _LIBCPP_STD_VER >= 26
 template <class _Tp>
 concept __integral_constant_like =
-    is_integral_v<decltype(_Tp::value)> && !is_same_v<bool, remove_const_t<decltype(_Tp::value)>> &&
+    is_integral_v<remove_cvref_t<decltype(_Tp::value)>> && !is_same_v<bool, remove_const_t<decltype(_Tp::value)>> &&
     convertible_to<_Tp, decltype(_Tp::value)> && equality_comparable_with<_Tp, decltype(_Tp::value)> &&
     bool_constant<_Tp() == _Tp::value>::value &&
     bool_constant<static_cast<decltype(_Tp::value)>(_Tp()) == _Tp::value>::value;
diff --git a/libcxx/include/utility b/libcxx/include/utility
index 1b19243afca1b..24b3baac91b1d 100644
--- a/libcxx/include/utility
+++ b/libcxx/include/utility
@@ -195,6 +195,22 @@ template<class T1, class T2>
 template<class T1, class T2>
     constexpr const T1&& get(const pair<T2, T1>&&) noexcept; // C++14
 
+// [const.wrap.class], class template constant_wrapper
+template<class T>
+  struct cw-fixed-value;                                    // exposition only, since C++26
+
+template<cw-fixed-value X, class = typename decltype(X)::type>
+  struct constant_wrapper;                                  // since C++26
+
+template<class T>
+  concept constexpr-param =                                 // exposition only, since C++26
+    requires { typename constant_wrapper<T::value>; };
+
+struct cw-operators;                                        // exposition only, since C++26
+
+template<cw-fixed-value X>
+  constexpr auto cw = constant_wrapper<X>{};                // since C++26
+
 // C++14
 
 template<class T, T... I>
@@ -292,6 +308,7 @@ template <class T>
 #  endif
 
 #  if _LIBCPP_STD_VER >= 26
+#    include <__utility/constant_wrapper.h>
 #    include <__variant/monostate.h>
 #  endif
 
diff --git a/libcxx/include/version b/libcxx/include/version
index 395330df7f237..8901f8f146d52 100644
--- a/libcxx/include/version
+++ b/libcxx/include/version
@@ -64,6 +64,7 @@ __cpp_lib_common_reference                              202302L <type_traits>
 __cpp_lib_common_reference_wrapper                      202302L <functional>
 __cpp_lib_complex_udls                                  201309L <complex>
 __cpp_lib_concepts                                      202207L <concepts>
+__cpp_lib_constant_wrapper                              202603L <utility>
 __cpp_lib_constexpr_algorithms                          202306L <algorithm> <utility>
                                                         201806L // C++20
 __cpp_lib_constexpr_bitset                              202207L <bitset>
@@ -555,6 +556,7 @@ __cpp_lib_void_t                                        201411L <type_traits>
 # undef  __cpp_lib_bind_front
 # define __cpp_lib_bind_front                           202306L
 # define __cpp_lib_bitset                               202306L
+# define __cpp_lib_constant_wrapper                     202603L
 # undef  __cpp_lib_constexpr_algorithms
 # define __cpp_lib_constexpr_algorithms                 202306L
 # define __cpp_lib_constexpr_flat_map                   202502L
diff --git a/libcxx/test/std/containers/views/views.span/span.cons/deduct.pass.cpp b/libcxx/test/std/containers/views/views.span/span.cons/deduct.pass.cpp
index 28dda866371f5..1fbd288894191 100644
--- a/libcxx/test/std/containers/views/views.span/span.cons/deduct.pass.cpp
+++ b/libcxx/test/std/containers/views/views.span/span.cons/deduct.pass.cpp
@@ -30,6 +30,7 @@
 #include <iterator>
 #include <memory>
 #include <string>
+#include <utility>
 #include <type_traits>
 
 #include "test_macros.h"
@@ -57,6 +58,13 @@ void test_iterator_sentinel() {
     assert(s.size() == std::size(arr));
     assert(s.data() == std::data(arr));
   }
+
+  {
+    std::span s{std::begin(arr), std::cw<3>};
+    ASSERT_SAME_TYPE(decltype(s), std::span<int, 3>);
+    assert(s.size() == std::size(arr));
+    assert(s.data() == std::data(arr));
+  }
 #endif
 }
 
diff --git a/libcxx/test/std/language.support/support.limits/support.limits.general/utility.version.compile.pass.cpp b/libcxx/test/std/language.support/support.limits/support.limits.general/utility.version.compile.pass.cpp
index 3f82018a15063..8cbd16d242f74 100644
--- a/libcxx/test/std/language.support/support.limits/support.limits.general/utility.version.compile.pass.cpp
+++ b/libcxx/test/std/language.support/support.limits/support.limits.general/utility.version.compile.pass.cpp
@@ -24,6 +24,10 @@
 #    error "__cpp_lib_as_const should not be defined before c++17"
 #  endif
 
+#  ifdef __cpp_lib_constant_wrapper
+#    error "__cpp_lib_constant_wrapper should not be defined before c++26"
+#  endif
+
 #  ifdef __cpp_lib_constexpr_algorithms
 #    error "__cpp_lib_constexpr_algorithms should not be defined before c++20"
 #  endif
@@ -78,6 +82,10 @@
 #    error "__cpp_lib_as_const should not be defined before c++17"
 #  endif
 
+#  ifdef __cpp_lib_constant_wrapper
+#    error "__cpp_lib_constant_wrapper should not be defined before c++26"
+#  endif
+
 #  ifdef __cpp_lib_constexpr_algorithms
 #    error "__cpp_lib_constexpr_algorithms should not be defined before c++20"
 #  endif
@@ -144,6 +152,10 @@
 #    error "__cpp_lib_as_const should have the value 201510L in c++17"
 #  endif
 
+#  ifdef __cpp_lib_constant_wrapper
+#    error "__cpp_lib_constant_wrapper should not be defined before c++26"
+#  endif
+
 #  ifdef __cpp_lib_constexpr_algorithms
 #    error "__cpp_lib_constexpr_algorithms should not be defined before c++20"
 #  endif
@@ -210,6 +222,10 @@
 #    error "__cpp_lib_as_const should have the value 201510L in c++20"
 #  endif
 
+#  ifdef __cpp_lib_constant_wrapper
+#    error "__cpp_lib_constant_wrapper should not be defined before c++26"
+#  endif
+
 #  ifndef __cpp_lib_constexpr_algorithms
 #    error "__cpp_lib_constexpr_algorithms should be defined in c++20"
 #  endif
@@ -285,6 +301,10 @@
 #    error "__cpp_lib_as_const should have the value 201510L in c++23"
 #  endif
 
+#  ifdef __cpp_lib_constant_wrapper
+#    error "__cpp_lib_constant_wrapper should not be defined before c++26"
+#  endif
+
 #  ifndef __cpp_lib_constexpr_algorithms
 #    error "__cpp_lib_constexpr_algorithms should be defined in c++23"
 #  endif
@@ -381,6 +401,13 @@
 #    error "__cpp_lib_as_const should have the value 201510L in c++26"
 #  endif
 
+#  ifndef __cpp_lib_constant_wrapper
+#    error "__cpp_lib_constant_wrapper should be defined in c++26"
+#  endif
+#  if __cpp_lib_constant_wrapper != 202603L
+#    error "__cpp_lib_constant_wrapper should have the value 202603L in c++26"
+#  endif
+
 #  ifndef __cpp_lib_constexpr_algorithms
 #    error "__cpp_lib_constexpr_algorithms should be defined in c++26"
 #  endif
diff --git a/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp b/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp
index d1c571a9d1a8c..e342a7ba33f9e 100644
--- a/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp
+++ b/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp
@@ -180,6 +180,10 @@
 #    error "__cpp_lib_concepts should not be defined before c++20"
 #  endif
 
+#  ifdef __cpp_lib_constant_wrapper
+#    error "__cpp_lib_constant_wrapper should not be defined before c++26"
+#  endif
+
 #  ifdef __cpp_lib_constexpr_algorithms
 #    error "__cpp_lib_constexpr_algorithms should not be defined before c++20"
 #  endif
@@ -1100,6 +1104,10 @@
 #    error "__cpp_lib_concepts should not be defined before c++20"
 #  endif
 
+#  ifdef __cpp_lib_constant_wrapper
+#    error "__cpp_lib_constant_wrapper should not be defined before c++26"
+#  endif
+
 #  ifdef __cpp_lib_constexpr_algorithms
 #    error "__cpp_lib_constexpr_algorithms should not be defined before c++20"
 #  endif
@@ -2122,6 +2130,10 @@
 #    error "__cpp_lib_concepts should not be defined before c++20"
 #  endif
 
+#  ifdef __cpp_lib_constant_wrapper
+#    error "__cpp_lib_constant_wrapper should not be defined before c++26"
+#  endif
+
 #  ifdef __cpp_lib_constexpr_algorithms
 #    error "__cpp_lib_constexpr_algorithms should not be defined before c++20"
 #  endif
@@ -3369,6 +3381,10 @@
 #    error "__cpp_lib_concepts should have the value 202207L in c++20"
 #  endif
 
+#  ifdef __cpp_lib_constant_wrapper
+#    error "__cpp_lib_constant_wrapper should not be defined before c++26"
+#  endif
+
 #  ifndef __cpp_lib_constexpr_algorithms
 #    error "__cpp_lib_constexpr_algorithms should be defined in c++20"
 #  endif
@@ -4832,6 +4848,10 @@
 #    error "__cpp_lib_concepts should have the value 202207L in c++23"
 #  endif
 
+#  ifdef __cpp_lib_constant_wrapper
+#    error "__cpp_lib_constant_wrapper should not be defined before c++26"
+#  endif
+
 #  ifndef __cpp_lib_constexpr_algorithms
 #    error "__cpp_lib_constexpr_algorithms should be defined in c++23"
 #  endif
@@ -6517,6 +6537,13 @@
 #    error "__cpp_lib_concepts should have the value 202207L in c++26"
 #  endif
 
+#  ifndef __cpp_lib_constant_wrapper
+#    error "__cpp_lib_constant_wrapper should be defined in c++26"
+#  endif
+#  if __cpp_lib_constant_wrapper != 202603L
+#    error "__cpp_lib_constant_wrapper should have the value 202603L in c++26"
+#  endif
+
 #  ifndef __cpp_lib_constexpr_algorithms
 #    error "__cpp_lib_constexpr_algorithms should be defined in c++26"
 #  endif
diff --git a/libcxx/test/std/utilities/const.wrap.class/adl.pass.cpp b/libcxx/test/std/utilities/const.wrap.class/adl.pass.cpp
new file mode 100644
index 0000000000000..46653e2128497
--- /dev/null
+++ b/libcxx/test/std/utilities/const.wrap.class/adl.pass.cpp
@@ -0,0 +1,54 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+
+// constant_wrapper
+
+// [Note 1: The unnamed second template parameter to constant_wrapper is present
+// to aid argument-dependent lookup ([basic.lookup.argdep]) in finding overloads
+// for which constant_wrapper's wrapped value is a suitable argument, but for which
+// the constant_wrapper itself is not. — end note]
+
+#include <cassert>
+#include <concepts>
+#include <type_traits>
+#include <utility>
+
+#include "helpers.h"
+#include "test_macros.h"
+
+namespace MyNamespace {
+struct MyType {
+  int value;
+
+  constexpr MyType(int v = 0) : value(v) {}
+};
+
+constexpr int adl_function(MyType mt) { return mt.value * 2; }
+
+} // namespace MyNamespace
+
+constexpr bool test() {
+  {
+    constexpr MyNamespace::MyType mt{21};
+    std::constant_wrapper<mt> cw_mt;
+
+    std::same_as<int> decltype(auto) result = adl_function(cw_mt);
+    assert(result == 42);
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}
diff --git a/libcxx/test/std/utilities/const.wrap.class/assign.pass.cpp b/libcxx/test/std/utilities/const.wrap.class/assign.pass.cpp
new file mode 100644
index 0000000000000..b5a11d837bb64
--- /dev/null
+++ b/libcxx/test/std/utilities/const.wrap.class/assign.pass.cpp
@@ -0,0 +1,86 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+
+// constant_wrapper
+
+// template<constexpr-param R>
+//   constexpr auto operator=(R) const noexcept
+//     -> constant_wrapper<value = R::value> { return {}; }
+
+#include <cassert>
+#include <concepts>
+#include <type_traits>
+#include <utility>
+
+#include "helpers.h"
+#include "test_macros.h"
+
+struct WithOps {
+  int value;
+
+  constexpr WithOps(int v) : value(v) {}
+
+  constexpr WithOps operator=(int i) const { return WithOps{value + i}; }
+};
+
+struct OptsReturnNonStructural {
+  int value;
+
+  constexpr OptsReturnNonStructural(int v) : value(v) {}
+
+  constexpr NonStructural operator=(int i) const { return NonStructural{value + i}; }
+};
+
+template <class T, class R>
+concept HasAssign = requires(const T t, R r) {
+  { t = r };
+};
+
+template <class T, class R>
+concept HasNoexceptAssign = requires(const T t, R r) {
+  { t = r } noexcept;
+};
+
+static_assert(!HasAssign<std::constant_wrapper<5>, std::constant_wrapper<3>>);
+static_assert(!HasNoexceptAssign<std::constant_wrapper<5>, std::constant_wrapper<3>>);
+
+static_assert(HasAssign<std::constant_wrapper<WithOps{5}>, std::constant_wrapper<3>>);
+static_assert(HasNoexceptAssign<std::constant_wrapper<WithOps{5}>, std::constant_wrapper<3>>);
+
+static_assert(!HasAssign<std::constant_wrapper<OptsReturnNonStructural{5}>, std::constant_wrapper<5>>);
+
+constexpr bool test() {
+  {
+    // WithOps assignment
+    const std::constant_wrapper<WithOps{5}> cwOps5;
+    std::constant_wrapper<3> cw3;
+
+    std::same_as<std::constant_wrapper<WithOps{8}>> decltype(auto) result = cwOps5 = cw3;
+    static_assert(result.value.value == 8);
+  }
+
+  {
+    // with integral_constant
+    const std::constant_wrapper<WithOps{5}> cwOps5;
+    std::integral_constant<int, 3> ic3;
+
+    std::same_as<std::constant_wrapper<WithOps{8}>> decltype(auto) result = cwOps5 = ic3;
+    static_assert(result.value.value == 8);
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}
diff --git a/libcxx/test/std/utilities/const.wrap.class/binary_ops.pass.cpp b/libcxx/test/std/utilities/const.wrap.class/binary_ops.pass.cpp
new file mode 100644
index 0000000000000..d33bb207ef95f
--- /dev/null
+++ b/libcxx/test/std/utilities/const.wrap.class/binary_ops.pass.cpp
@@ -0,0 +1,590 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+// ADDITIONAL_COMPILE_FLAGS: -Wno-constant-logical-operand
+
+// constant_wrapper
+
+//  template<constexpr-param L, constexpr-param R>
+//    friend constexpr auto operator+(L, R) noexcept -> constant_wrapper<(L::value + R::value)>
+//      { return {}; }
+//  template<constexpr-param L, constexpr-param R>
+//    friend constexpr auto operator-(L, R) noexcept -> constant_wrapper<(L::value - R::value)>
+//      { return {}; }
+//  template<constexpr-param L, constexpr-param R>
+//    friend constexpr auto operator*(L, R) noexcept -> constant_wrapper<(L::value * R::value)>
+//      { return {}; }
+//  template<constexpr-param L, constexpr-param R>
+//    friend constexpr auto operator/(L, R) noexcept -> constant_wrapper<(L::value / R::value)>
+//      { return {}; }
+//  template<constexpr-param L, constexpr-param R>
+//    friend constexpr auto operator%(L, R) noexcept -> constant_wrapper<(L::value % R::value)>
+//      { return {}; }
+
+//  template<constexpr-param L, constexpr-param R>
+//    friend constexpr auto operator<<(L, R) noexcept -> constant_wrapper<(L::value << R::value)>
+//      { return {}; }
+//  template<constexpr-param L, constexpr-param R>
+//    friend constexpr auto operator>>(L, R) noexcept -> constant_wrapper<(L::value >> R::value)>
+//      { return {}; }
+//  template<constexpr-param L, constexpr-param R>
+//    friend constexpr auto operator&(L, R) noexcept -> constant_wrapper<(L::value & R::value)>
+//      { return {}; }
+//  template<constexpr-param L, constexpr-param R>
+//    friend constexpr auto operator|(L, R) noexcept -> constant_wrapper<(L::value | R::value)>
+//      { return {}; }
+//  template<constexpr-param L, constexpr-param R>
+//    friend constexpr auto operator^(L, R) noexcept -> constant_wrapper<(L::value ^ R::value)>
+//      { return {}; }
+
+//  template<constexpr-param L, 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<constexpr-param L, 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 {}; }
+
+#include <cassert>
+#include <concepts>
+#include <functional>
+#include <type_traits>
+#include <utility>
+
+#include "helpers.h"
+#include "test_macros.h"
+
+struct WithOps {
+  int value;
+
+  constexpr WithOps(int v) : value(v) {}
+
+  friend constexpr auto operator+(WithOps l, WithOps r) { return WithOps{l.value + r.value}; }
+  friend constexpr auto operator-(WithOps l, WithOps r) { return WithOps{l.value - r.value}; }
+  friend constexpr auto operator*(WithOps l, WithOps r) { return WithOps{l.value * r.value}; }
+  friend constexpr auto operator/(WithOps l, WithOps r) { return WithOps{l.value / r.value}; }
+  friend constexpr auto operator%(WithOps l, WithOps r) { return WithOps{l.value % r.value}; }
+  friend constexpr auto operator<<(WithOps l, WithOps r) { return WithOps{l.value << r.value}; }
+  friend constexpr auto operator>>(WithOps l, WithOps r) { return WithOps{l.value >> r.value}; }
+  friend constexpr auto operator&(WithOps l, WithOps r) { return WithOps{l.value & r.value}; }
+  friend constexpr auto operator|(WithOps l, WithOps r) { return WithOps{l.value | r.value}; }
+  friend constexpr auto operator^(WithOps l, WithOps r) { return WithOps{l.value ^ r.value}; }
+
+  friend constexpr auto operator&&(WithOps l, WithOps r) { return WithOps{l.value && r.value}; }
+  friend constexpr auto operator||(WithOps l, WithOps r) { return WithOps{l.value || r.value}; }
+};
+
+struct OptsReturnNonStructural {
+  int value;
+
+  constexpr OptsReturnNonStructural(int v) : value(v) {}
+
+  friend constexpr auto operator+(OptsReturnNonStructural l, OptsReturnNonStructural r) {
+    return NonStructural{l.value + r.value};
+  }
+  friend constexpr auto operator-(OptsReturnNonStructural l, OptsReturnNonStructural r) {
+    return NonStructural{l.value - r.value};
+  }
+  friend constexpr auto operator*(OptsReturnNonStructural l, OptsReturnNonStructural r) {
+    return NonStructural{l.value * r.value};
+  }
+  friend constexpr auto operator/(OptsReturnNonStructural l, OptsReturnNonStructural r) {
+    return NonStructural{l.value / r.value};
+  }
+  friend constexpr auto operator%(OptsReturnNonStructural l, OptsReturnNonStructural r) {
+    return NonStructural{l.value % r.value};
+  }
+  friend constexpr auto operator<<(OptsReturnNonStructural l, OptsReturnNonStructural r) {
+    return NonStructural{l.value << r.value};
+  }
+  friend constexpr auto operator>>(OptsReturnNonStructural l, OptsReturnNonStructural r) {
+    return NonStructural{l.value >> r.value};
+  }
+  friend constexpr auto operator&(OptsReturnNonStructural l, OptsReturnNonStructural r) {
+    return NonStructural{l.value & r.value};
+  }
+  friend constexpr auto operator|(OptsReturnNonStructural l, OptsReturnNonStructural r) {
+    return NonStructural{l.value | r.value};
+  }
+  friend constexpr auto operator^(OptsReturnNonStructural l, OptsReturnNonStructural r) {
+    return NonStructural{l.value ^ r.value};
+  }
+  friend constexpr auto operator&&(OptsReturnNonStructural l, OptsReturnNonStructural r) {
+    return NonStructural{l.value && r.value};
+  }
+  friend constexpr auto operator||(OptsReturnNonStructural l, OptsReturnNonStructural r) {
+    return NonStructural{l.value || r.value};
+  }
+};
+
+struct NoOps {};
+
+template <class L, class R>
+concept HasPlus = requires(L l, R r) {
+  { l + r };
+};
+
+template <class L, class R>
+concept HasMinus = requires(L l, R r) {
+  { l - r };
+};
+
+template <class L, class R>
+concept HasMultiply = requires(L l, R r) {
+  { l * r };
+};
+
+template <class L, class R>
+concept HasDivide = requires(L l, R r) {
+  { l / r };
+};
+
+template <class L, class R>
+concept HasModulo = requires(L l, R r) {
+  { l % r };
+};
+
+template <class L, class R>
+concept HasShiftLeft = requires(L l, R r) {
+  { l << r };
+};
+
+template <class L, class R>
+concept HasShiftRight = requires(L l, R r) {
+  { l >> r };
+};
+
+template <class L, class R>
+concept HasBitAnd = requires(L l, R r) {
+  { l & r };
+};
+
+template <class L, class R>
+concept HasBitOr = requires(L l, R r) {
+  { l | r };
+};
+
+template <class L, class R>
+concept HasBitXor = requires(L l, R r) {
+  { l ^ r };
+};
+
+template <class L, class R>
+concept HasLogicalAnd = requires(L l, R r) {
+  { l && r };
+};
+
+template <class L, class R>
+concept HasLogicalOr = requires(L l, R r) {
+  { l || r };
+};
+
+template <class L, class R>
+concept HasNoexceptPlus = requires(L l, R r) {
+  { l + r } noexcept;
+};
+
+template <class L, class R>
+concept HasNoexceptMinus = requires(L l, R r) {
+  { l - r } noexcept;
+};
+
+template <class L, class R>
+concept HasNoexceptMultiply = requires(L l, R r) {
+  { l * r } noexcept;
+};
+
+template <class L, class R>
+concept HasNoexceptDivide = requires(L l, R r) {
+  { l / r } noexcept;
+};
+
+template <class L, class R>
+concept HasNoexceptModulo = requires(L l, R r) {
+  { l % r } noexcept;
+};
+
+template <class L, class R>
+concept HasNoexceptShiftLeft = requires(L l, R r) {
+  { l << r } noexcept;
+};
+
+template <class L, class R>
+concept HasNoexceptShiftRight = requires(L l, R r) {
+  { l >> r } noexcept;
+};
+
+template <class L, class R>
+concept HasNoexceptBitAnd = requires(L l, R r) {
+  { l & r } noexcept;
+};
+
+template <class L, class R>
+concept HasNoexceptBitOr = requires(L l, R r) {
+  { l | r } noexcept;
+};
+
+template <class L, class R>
+concept HasNoexceptBitXor = requires(L l, R r) {
+  { l ^ r } noexcept;
+};
+
+template <class L, class R>
+concept HasNoexceptLogicalAnd = requires(L l, R r) {
+  { l && r } noexcept;
+};
+
+template <class L, class R>
+concept HasNoexceptLogicalOr = requires(L l, R r) {
+  { l || r } noexcept;
+};
+
+// Concept checks for int + int operations
+static_assert(HasPlus<std::constant_wrapper<6>, std::constant_wrapper<3>>);
+static_assert(HasMinus<std::constant_wrapper<6>, std::constant_wrapper<3>>);
+static_assert(HasMultiply<std::constant_wrapper<6>, std::constant_wrapper<3>>);
+static_assert(HasDivide<std::constant_wrapper<6>, std::constant_wrapper<3>>);
+static_assert(HasModulo<std::constant_wrapper<6>, std::constant_wrapper<3>>);
+static_assert(HasShiftLeft<std::constant_wrapper<6>, std::constant_wrapper<1>>);
+static_assert(HasShiftRight<std::constant_wrapper<6>, std::constant_wrapper<1>>);
+static_assert(HasBitAnd<std::constant_wrapper<6>, std::constant_wrapper<3>>);
+static_assert(HasBitOr<std::constant_wrapper<6>, std::constant_wrapper<3>>);
+static_assert(HasBitXor<std::constant_wrapper<6>, std::constant_wrapper<3>>);
+static_assert(HasLogicalAnd<std::constant_wrapper<6>, std::constant_wrapper<3>>);
+static_assert(HasLogicalOr<std::constant_wrapper<6>, std::constant_wrapper<3>>);
+
+static_assert(HasNoexceptPlus<std::constant_wrapper<6>, std::constant_wrapper<3>>);
+static_assert(HasNoexceptMinus<std::constant_wrapper<6>, std::constant_wrapper<3>>);
+static_assert(HasNoexceptMultiply<std::constant_wrapper<6>, std::constant_wrapper<3>>);
+static_assert(HasNoexceptDivide<std::constant_wrapper<6>, std::constant_wrapper<3>>);
+static_assert(HasNoexceptModulo<std::constant_wrapper<6>, std::constant_wrapper<3>>);
+static_assert(HasNoexceptShiftLeft<std::constant_wrapper<6>, std::constant_wrapper<1>>);
+static_assert(HasNoexceptShiftRight<std::constant_wrapper<6>, std::constant_wrapper<1>>);
+static_assert(HasNoexceptBitAnd<std::constant_wrapper<6>, std::constant_wrapper<3>>);
+static_assert(HasNoexceptBitOr<std::constant_wrapper<6>, std::constant_wrapper<3>>);
+static_assert(HasNoexceptBitXor<std::constant_wrapper<6>, std::constant_wrapper<3>>);
+static_assert(HasNoexceptLogicalAnd<std::constant_wrapper<6>, std::constant_wrapper<3>>);
+static_assert(HasNoexceptLogicalOr<std::constant_wrapper<6>, std::constant_wrapper<3>>);
+
+// NoOps
+static_assert(!HasPlus<std::constant_wrapper<NoOps{}>, std::constant_wrapper<NoOps{}>>);
+static_assert(!HasMinus<std::constant_wrapper<NoOps{}>, std::constant_wrapper<NoOps{}>>);
+static_assert(!HasMultiply<std::constant_wrapper<NoOps{}>, std::constant_wrapper<NoOps{}>>);
+static_assert(!HasDivide<std::constant_wrapper<NoOps{}>, std::constant_wrapper<NoOps{}>>);
+static_assert(!HasModulo<std::constant_wrapper<NoOps{}>, std::constant_wrapper<NoOps{}>>);
+static_assert(!HasShiftLeft<std::constant_wrapper<NoOps{}>, std::constant_wrapper<NoOps{}>>);
+static_assert(!HasShiftRight<std::constant_wrapper<NoOps{}>, std::constant_wrapper<NoOps{}>>);
+static_assert(!HasBitAnd<std::constant_wrapper<NoOps{}>, std::constant_wrapper<NoOps{}>>);
+static_assert(!HasBitOr<std::constant_wrapper<NoOps{}>, std::constant_wrapper<NoOps{}>>);
+static_assert(!HasBitXor<std::constant_wrapper<NoOps{}>, std::constant_wrapper<NoOps{}>>);
+static_assert(!HasLogicalAnd<std::constant_wrapper<NoOps{}>, std::constant_wrapper<NoOps{}>>);
+static_assert(!HasLogicalOr<std::constant_wrapper<NoOps{}>, std::constant_wrapper<NoOps{}>>);
+
+// Concept checks for WithOps operations
+static_assert(HasNoexceptPlus<std::constant_wrapper<WithOps{6}>, std::constant_wrapper<WithOps{3}>>);
+static_assert(HasNoexceptMinus<std::constant_wrapper<WithOps{6}>, std::constant_wrapper<WithOps{3}>>);
+static_assert(HasNoexceptMultiply<std::constant_wrapper<WithOps{6}>, std::constant_wrapper<WithOps{3}>>);
+static_assert(HasNoexceptDivide<std::constant_wrapper<WithOps{6}>, std::constant_wrapper<WithOps{3}>>);
+static_assert(HasNoexceptModulo<std::constant_wrapper<WithOps{6}>, std::constant_wrapper<WithOps{3}>>);
+static_assert(HasNoexceptShiftLeft<std::constant_wrapper<WithOps{6}>, std::constant_wrapper<WithOps{1}>>);
+static_assert(HasNoexceptShiftRight<std::constant_wrapper<WithOps{6}>, std::constant_wrapper<WithOps{1}>>);
+static_assert(HasNoexceptBitAnd<std::constant_wrapper<WithOps{6}>, std::constant_wrapper<WithOps{3}>>);
+static_assert(HasNoexceptBitOr<std::constant_wrapper<WithOps{6}>, std::constant_wrapper<WithOps{3}>>);
+static_assert(HasNoexceptBitXor<std::constant_wrapper<WithOps{6}>, std::constant_wrapper<WithOps{3}>>);
+static_assert(HasNoexceptLogicalAnd<std::constant_wrapper<WithOps{6}>, std::constant_wrapper<WithOps{3}>>);
+static_assert(HasNoexceptLogicalOr<std::constant_wrapper<WithOps{6}>, std::constant_wrapper<WithOps{3}>>);
+
+// Non-structural types use implicit conversion to underlying type
+static_assert(
+    HasPlus<std::constant_wrapper<OptsReturnNonStructural{6}>, std::constant_wrapper<OptsReturnNonStructural{3}>>);
+static_assert(
+    HasMinus<std::constant_wrapper<OptsReturnNonStructural{6}>, std::constant_wrapper<OptsReturnNonStructural{3}>>);
+static_assert(
+    HasMultiply<std::constant_wrapper<OptsReturnNonStructural{6}>, std::constant_wrapper<OptsReturnNonStructural{3}>>);
+static_assert(
+    HasDivide<std::constant_wrapper<OptsReturnNonStructural{6}>, std::constant_wrapper<OptsReturnNonStructural{3}>>);
+static_assert(
+    HasModulo<std::constant_wrapper<OptsReturnNonStructural{6}>, std::constant_wrapper<OptsReturnNonStructural{3}>>);
+static_assert(
+    HasShiftLeft<std::constant_wrapper<OptsReturnNonStructural{6}>, std::constant_wrapper<OptsReturnNonStructural{1}>>);
+static_assert(HasShiftRight<std::constant_wrapper<OptsReturnNonStructural{6}>,
+                            std::constant_wrapper<OptsReturnNonStructural{1}>>);
+static_assert(
+    HasBitAnd<std::constant_wrapper<OptsReturnNonStructural{6}>, std::constant_wrapper<OptsReturnNonStructural{3}>>);
+static_assert(
+    HasBitOr<std::constant_wrapper<OptsReturnNonStructural{6}>, std::constant_wrapper<OptsReturnNonStructural{3}>>);
+static_assert(
+    HasBitXor<std::constant_wrapper<OptsReturnNonStructural{6}>, std::constant_wrapper<OptsReturnNonStructural{3}>>);
+static_assert(HasLogicalAnd<std::constant_wrapper<OptsReturnNonStructural{6}>,
+                            std::constant_wrapper<OptsReturnNonStructural{3}>>);
+static_assert(
+    HasLogicalOr<std::constant_wrapper<OptsReturnNonStructural{6}>, std::constant_wrapper<OptsReturnNonStructural{3}>>);
+
+static_assert(!HasNoexceptPlus<std::constant_wrapper<OptsReturnNonStructural{6}>,
+                               std::constant_wrapper<OptsReturnNonStructural{3}>>);
+static_assert(!HasNoexceptMinus<std::constant_wrapper<OptsReturnNonStructural{6}>,
+                                std::constant_wrapper<OptsReturnNonStructural{3}>>);
+static_assert(!HasNoexceptMultiply<std::constant_wrapper<OptsReturnNonStructural{6}>,
+                                   std::constant_wrapper<OptsReturnNonStructural{3}>>);
+static_assert(!HasNoexceptDivide<std::constant_wrapper<OptsReturnNonStructural{6}>,
+                                 std::constant_wrapper<OptsReturnNonStructural{3}>>);
+static_assert(!HasNoexceptModulo<std::constant_wrapper<OptsReturnNonStructural{6}>,
+                                 std::constant_wrapper<OptsReturnNonStructural{3}>>);
+static_assert(!HasNoexceptShiftLeft<std::constant_wrapper<OptsReturnNonStructural{6}>,
+                                    std::constant_wrapper<OptsReturnNonStructural{1}>>);
+static_assert(!HasNoexceptShiftRight<std::constant_wrapper<OptsReturnNonStructural{6}>,
+                                     std::constant_wrapper<OptsReturnNonStructural{1}>>);
+static_assert(!HasNoexceptBitAnd<std::constant_wrapper<OptsReturnNonStructural{6}>,
+                                 std::constant_wrapper<OptsReturnNonStructural{3}>>);
+static_assert(!HasNoexceptBitOr<std::constant_wrapper<OptsReturnNonStructural{6}>,
+                                std::constant_wrapper<OptsReturnNonStructural{3}>>);
+static_assert(!HasNoexceptBitXor<std::constant_wrapper<OptsReturnNonStructural{6}>,
+                                 std::constant_wrapper<OptsReturnNonStructural{3}>>);
+static_assert(!HasNoexceptLogicalAnd<std::constant_wrapper<OptsReturnNonStructural{6}>,
+                                     std::constant_wrapper<OptsReturnNonStructural{3}>>);
+static_assert(!HasNoexceptLogicalOr<std::constant_wrapper<OptsReturnNonStructural{6}>,
+                                    std::constant_wrapper<OptsReturnNonStructural{3}>>);
+
+constexpr bool test() {
+  {
+    // int + int
+    std::constant_wrapper<6> cw6;
+    std::constant_wrapper<3> cw3;
+
+    std::same_as<std::constant_wrapper<9>> decltype(auto) result = cw6 + cw3;
+    static_assert(result == 9);
+
+    std::same_as<std::constant_wrapper<3>> decltype(auto) result2 = cw6 - cw3;
+    static_assert(result2 == 3);
+
+    std::same_as<std::constant_wrapper<18>> decltype(auto) result3 = cw6 * cw3;
+    static_assert(result3 == 18);
+
+    std::same_as<std::constant_wrapper<2>> decltype(auto) result4 = cw6 / cw3;
+    static_assert(result4 == 2);
+
+    std::same_as<std::constant_wrapper<0>> decltype(auto) result5 = cw6 % cw3;
+    static_assert(result5 == 0);
+
+    std::same_as<std::constant_wrapper<2>> decltype(auto) result6 = cw6 & cw3;
+    static_assert(result6 == 2);
+
+    std::same_as<std::constant_wrapper<7>> decltype(auto) result7 = cw6 | cw3;
+    static_assert(result7 == 7);
+
+    std::same_as<std::constant_wrapper<5>> decltype(auto) result8 = cw6 ^ cw3;
+    static_assert(result8 == 5);
+
+    // Shift operations: 6 << 3 = 48, 6 >> 3 = 0
+    std::same_as<std::constant_wrapper<48>> decltype(auto) result9 = cw6 << cw3;
+    static_assert(result9 == 48);
+
+    std::same_as<std::constant_wrapper<0>> decltype(auto) result10 = cw6 >> cw3;
+    static_assert(result10 == 0);
+
+    // logical operations: int convertible to bool, so constant_wrapper overload is disabled
+    // They are implicitly converted to bool and use built-in operators, resulting in a bool
+    std::same_as<bool> decltype(auto) result11 = cw6 && cw3;
+    assert(result11 == true);
+
+    std::constant_wrapper<0> cw0;
+    std::same_as<bool> decltype(auto) result12 = cw0 || cw3;
+    assert(result12 == true);
+  }
+
+  {
+    // WithOps operations
+    std::constant_wrapper<WithOps{6}> cwWithOps6;
+    std::constant_wrapper<WithOps{3}> cwWithOps3;
+
+    std::same_as<std::constant_wrapper<WithOps{9}>> decltype(auto) result = cwWithOps6 + cwWithOps3;
+    static_assert(result.value.value == 9);
+
+    std::same_as<std::constant_wrapper<WithOps{3}>> decltype(auto) result2 = cwWithOps6 - cwWithOps3;
+    static_assert(result2.value.value == 3);
+
+    std::same_as<std::constant_wrapper<WithOps{18}>> decltype(auto) result3 = cwWithOps6 * cwWithOps3;
+    static_assert(result3.value.value == 18);
+
+    std::same_as<std::constant_wrapper<WithOps{2}>> decltype(auto) result4 = cwWithOps6 / cwWithOps3;
+    static_assert(result4.value.value == 2);
+
+    std::same_as<std::constant_wrapper<WithOps{0}>> decltype(auto) result5 = cwWithOps6 % cwWithOps3;
+    static_assert(result5.value.value == 0);
+
+    std::same_as<std::constant_wrapper<WithOps{2}>> decltype(auto) result6 = cwWithOps6 & cwWithOps3;
+    static_assert(result6.value.value == 2);
+
+    std::same_as<std::constant_wrapper<WithOps{7}>> decltype(auto) result7 = cwWithOps6 | cwWithOps3;
+    static_assert(result7.value.value == 7);
+
+    std::same_as<std::constant_wrapper<WithOps{5}>> decltype(auto) result8 = cwWithOps6 ^ cwWithOps3;
+    static_assert(result8.value.value == 5);
+
+    // Shift operations: 6 << 3 = 48, 6 >> 3 = 0
+    std::same_as<std::constant_wrapper<WithOps{48}>> decltype(auto) result9 = cwWithOps6 << cwWithOps3;
+    static_assert(result9.value.value == 48);
+
+    std::same_as<std::constant_wrapper<WithOps{0}>> decltype(auto) result10 = cwWithOps6 >> cwWithOps3;
+    static_assert(result10.value.value == 0);
+
+    std::same_as<std::constant_wrapper<WithOps{1}>> decltype(auto) result11 = cwWithOps6 && cwWithOps3;
+    static_assert(result11.value.value == 1);
+
+    std::same_as<std::constant_wrapper<WithOps{1}>> decltype(auto) result12 = cwWithOps6 || cwWithOps3;
+    static_assert(result12.value.value == 1);
+  }
+
+  {
+    // Non-structural return types use implicit conversion
+    std::constant_wrapper<OptsReturnNonStructural{6}> cwOpt6;
+    std::constant_wrapper<OptsReturnNonStructural{3}> cwOpt3;
+
+    std::same_as<NonStructural> decltype(auto) result = cwOpt6 + cwOpt3;
+    assert(result.get() == 9);
+
+    std::same_as<NonStructural> decltype(auto) result2 = cwOpt6 - cwOpt3;
+    assert(result2.get() == 3);
+
+    std::same_as<NonStructural> decltype(auto) result3 = cwOpt6 * cwOpt3;
+    assert(result3.get() == 18);
+
+    std::same_as<NonStructural> decltype(auto) result4 = cwOpt6 / cwOpt3;
+    assert(result4.get() == 2);
+
+    std::same_as<NonStructural> decltype(auto) result5 = cwOpt6 % cwOpt3;
+    assert(result5.get() == 0);
+
+    std::same_as<NonStructural> decltype(auto) result6 = cwOpt6 & cwOpt3;
+    assert(result6.get() == 2);
+
+    std::same_as<NonStructural> decltype(auto) result7 = cwOpt6 | cwOpt3;
+    assert(result7.get() == 7);
+
+    std::same_as<NonStructural> decltype(auto) result8 = cwOpt6 ^ cwOpt3;
+    assert(result8.get() == 5);
+
+    // Shift operations: 6 << 3 = 48, 6 >> 3 = 0
+    std::same_as<NonStructural> decltype(auto) result9 = cwOpt6 << cwOpt3;
+    assert(result9.get() == 48);
+
+    std::same_as<NonStructural> decltype(auto) result10 = cwOpt6 >> cwOpt3;
+    assert(result10.get() == 0);
+
+    std::same_as<NonStructural> decltype(auto) result11 = cwOpt6 && cwOpt3;
+    assert(result11.get() == 1);
+
+    std::same_as<NonStructural> decltype(auto) result12 = cwOpt6 || cwOpt3;
+    assert(result12.get() == 1);
+  }
+
+  {
+    // Mix with runtime param: these operators are not used
+    std::constant_wrapper<6> cw6;
+    int i = 3;
+
+    std::same_as<int> decltype(auto) result = cw6 + i;
+    assert(result == 9);
+
+    std::same_as<int> decltype(auto) result2 = cw6 - i;
+    assert(result2 == 3);
+
+    std::same_as<int> decltype(auto) result3 = cw6 * i;
+    assert(result3 == 18);
+
+    std::same_as<int> decltype(auto) result4 = cw6 / i;
+    assert(result4 == 2);
+
+    std::same_as<int> decltype(auto) result5 = cw6 % i;
+    assert(result5 == 0);
+
+    std::same_as<int> decltype(auto) result6 = cw6 & i;
+    assert(result6 == 2);
+
+    std::same_as<int> decltype(auto) result7 = cw6 | i;
+    assert(result7 == 7);
+
+    std::same_as<int> decltype(auto) result8 = cw6 ^ i;
+    assert(result8 == 5);
+
+    // Shift operations: 6 << 3 = 48, 6 >> 3 = 0
+    std::same_as<int> decltype(auto) result9 = cw6 << i;
+    assert(result9 == 48);
+
+    std::same_as<int> decltype(auto) result10 = cw6 >> i;
+    assert(result10 == 0);
+
+    std::same_as<bool> decltype(auto) result11 = cw6 && i;
+    assert(result11 == true);
+
+    std::constant_wrapper<0> cw0;
+    std::same_as<bool> decltype(auto) result12 = cw0 || i;
+    assert(result12 == true);
+  }
+
+  {
+    // with integral_constant
+    std::constant_wrapper<6> cw6;
+    std::integral_constant<int, 3> ic3;
+
+    std::same_as<std::constant_wrapper<9>> decltype(auto) result = cw6 + ic3;
+    static_assert(result == 9);
+
+    std::same_as<std::constant_wrapper<3>> decltype(auto) result2 = cw6 - ic3;
+    static_assert(result2 == 3);
+
+    std::same_as<std::constant_wrapper<18>> decltype(auto) result3 = cw6 * ic3;
+    static_assert(result3 == 18);
+
+    std::same_as<std::constant_wrapper<2>> decltype(auto) result4 = cw6 / ic3;
+    static_assert(result4 == 2);
+
+    std::same_as<std::constant_wrapper<0>> decltype(auto) result5 = cw6 % ic3;
+    static_assert(result5 == 0);
+
+    std::same_as<std::constant_wrapper<2>> decltype(auto) result6 = cw6 & ic3;
+    static_assert(result6 == 2);
+
+    std::same_as<std::constant_wrapper<7>> decltype(auto) result7 = cw6 | ic3;
+    static_assert(result7 == 7);
+
+    std::same_as<std::constant_wrapper<5>> decltype(auto) result8 = cw6 ^ ic3;
+    static_assert(result8 == 5);
+
+    // Shift operations: 6 << 3 = 48, 6 >> 3 = 0
+    std::same_as<std::constant_wrapper<48>> decltype(auto) result9 = cw6 << ic3;
+    static_assert(result9 == 48);
+
+    std::same_as<std::constant_wrapper<0>> decltype(auto) result10 = cw6 >> ic3;
+    static_assert(result10 == 0);
+
+    // logical operations: int convertible to bool, so constant_wrapper overload is disabled
+    // They are implicitly converted to bool and use built-in operators, resulting in a bool
+    std::same_as<bool> decltype(auto) result11 = cw6 && ic3;
+    assert(result11 == true);
+
+    std::constant_wrapper<0> cw0;
+    std::same_as<bool> decltype(auto) result12 = cw0 || ic3;
+    assert(result12 == true);
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}
diff --git a/libcxx/test/std/utilities/const.wrap.class/call.pass.cpp b/libcxx/test/std/utilities/const.wrap.class/call.pass.cpp
new file mode 100644
index 0000000000000..98ea67bc959a8
--- /dev/null
+++ b/libcxx/test/std/utilities/const.wrap.class/call.pass.cpp
@@ -0,0 +1,239 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+
+// constant_wrapper
+
+// template<class... Args>
+// static constexpr decltype(auto) operator()(Args&&... args) noexcept(see below);
+
+#include <cassert>
+#include <concepts>
+#include <functional>
+#include <type_traits>
+#include <utility>
+
+#include "helpers.h"
+#include "MoveOnly.h"
+#include "test_macros.h"
+
+struct MoveOnlyFn {
+  constexpr MoveOnly operator()(const MoveOnly& m1, MoveOnly m2, MoveOnly&& m3) const {
+    return MoveOnly(m1.get() + m2.get() + m3.get());
+  }
+};
+
+constexpr bool fun_ptr(int i) { return i > 0; }
+
+struct OverloadSet {
+  constexpr int operator()(int) const { return 1; }
+
+  constexpr int operator()(std::constant_wrapper<42>) const { return 2; }
+};
+
+struct ReturnNonStructural {
+  constexpr NonStructural operator()(int i) const { return NonStructural{i}; }
+};
+
+struct CWOnly {
+  constexpr int operator()(std::constant_wrapper<42>) const { return 42; }
+};
+
+constexpr int nothrow_call(int) noexcept { return 42; }
+
+constexpr int throwing_call(int) { return 42; }
+
+struct S {
+  int member = 42;
+
+  constexpr int mem_fun(int i) const { return member + i; }
+};
+
+constexpr S s;
+
+// Let call-expr be constant_wrapper<INVOKE (value, remove_cvref_t<Args>::value...)>{} if all types
+// in remove_cvref_t<Args>... satisfy constexpr-param and constant_wrapper<INVOKE (value, remove_-
+// cvref_t<Args>::value...)> is a valid type, otherwise let call-expr be INVOKE (value, std::forward<Args>(args)...).
+//
+// Constraints: call-expr is a valid expression.
+// Remarks: The exception specification is equivalent to noexcept(call-expr).
+
+static_assert(std::is_invocable_v<std::constant_wrapper<[] { return 42; }>>);
+static_assert(!std::is_invocable_v<std::constant_wrapper<[] { return 42; }>, int>);
+static_assert(!std::is_invocable_v<std::constant_wrapper<5>>);
+
+static_assert(!std::is_invocable_v<std::constant_wrapper<std::plus<>{}>, int>);
+static_assert(std::is_nothrow_invocable_v<std::constant_wrapper<std::plus<>{}>, int, int>);
+static_assert(std::is_nothrow_invocable_v<std::constant_wrapper<std::plus<>{}>, std::constant_wrapper<42>, int>);
+static_assert(std::is_nothrow_invocable_v<std::constant_wrapper<std::plus<>{}>,
+                                          std::constant_wrapper<42>,
+                                          std::constant_wrapper<42>>);
+
+static_assert(std::is_nothrow_invocable_v<std::constant_wrapper<nothrow_call>, int>);
+static_assert(std::is_nothrow_invocable_v<std::constant_wrapper<nothrow_call>, std::constant_wrapper<42>>);
+
+static_assert(std::is_invocable_v<std::constant_wrapper<throwing_call>, int>);
+static_assert(!std::is_nothrow_invocable_v<std::constant_wrapper<throwing_call>, int>);
+static_assert(std::is_nothrow_invocable_v<std::constant_wrapper<throwing_call>, std::constant_wrapper<42>>,
+              "the call expression is still nothrow because the constexpr path is taken");
+
+constexpr bool test() {
+  {
+    // with runtime param
+    using T                                 = std::constant_wrapper<std::plus<>{}>;
+    std::same_as<int> decltype(auto) result = T::operator()(1, 2);
+    assert(result == 3);
+  }
+
+  {
+    // with runtime param and constexpr param
+    using T                                 = std::constant_wrapper<std::plus<>{}>;
+    std::same_as<int> decltype(auto) result = T::operator()(std::cw<1>, 2);
+    assert(result == 3);
+  }
+
+  {
+    // with only constexpr param
+    using T                                                      = std::constant_wrapper<std::plus<>{}>;
+    std::same_as<std::constant_wrapper<3>> decltype(auto) result = T::operator()(std::cw<1>, std::cw<2>);
+    static_assert(result == 3);
+  }
+
+  {
+    // nullary
+    using T                                                       = std::constant_wrapper<[] { return 42; }>;
+    std::same_as<std::constant_wrapper<42>> decltype(auto) result = T::operator()();
+    static_assert(result == 42);
+  }
+
+  {
+    // return void with runtime param
+    using T = std::constant_wrapper<[](int) {}>;
+    T::operator()(5);
+    static_assert(std::same_as<void, decltype(T::operator()(5))>);
+  }
+
+  {
+    // return void with constexpr param
+    using T = std::constant_wrapper<[](int) {}>;
+    T::operator()(std::cw<5>);
+    static_assert(std::same_as<void, decltype(T::operator()(std::cw<5>))>);
+  }
+
+  {
+    // nullary return void
+    using T = std::constant_wrapper<[] {}>;
+    T::operator()();
+    static_assert(std::same_as<void, decltype(T::operator()())>);
+  }
+
+  {
+    // move only
+    using T = std::constant_wrapper<MoveOnlyFn{}>;
+    MoveOnly m1(1), m2(2), m3(3);
+    std::same_as<MoveOnly> decltype(auto) result = T::operator()(m1, std::move(m2), std::move(m3));
+    assert(result.get() == 6);
+  }
+
+  {
+    // function pointer
+    using T                                  = std::constant_wrapper<fun_ptr>;
+    std::same_as<bool> decltype(auto) result = T::operator()(5);
+    assert(result);
+  }
+
+  {
+    // function pointer with constexpr param
+    using T                                                         = std::constant_wrapper<fun_ptr>;
+    std::same_as<std::constant_wrapper<true>> decltype(auto) result = T::operator()(std::cw<5>);
+    static_assert(result);
+  }
+  {
+    // member ptr with runtime param
+    using T = std::constant_wrapper<&S::member>;
+    S s1;
+    std::same_as<int&> decltype(auto) result = T::operator()(s1);
+    assert(result == 42);
+    assert(&result == &s1.member);
+  }
+  {
+    // member ptr with constexpr param
+    using T                                                       = std::constant_wrapper<&S::member>;
+    std::same_as<std::constant_wrapper<42>> decltype(auto) result = T::operator()(std::cw<&s>);
+    static_assert(result == 42);
+  }
+  {
+    // member function ptr with runtime param
+    using T = std::constant_wrapper<&S::mem_fun>;
+    S s1;
+    std::same_as<int> decltype(auto) result = T::operator()(s1, 8);
+    assert(result == 50);
+  }
+  {
+    // member function ptr with constexpr param
+    using T                                                       = std::constant_wrapper<&S::mem_fun>;
+    std::same_as<std::constant_wrapper<50>> decltype(auto) result = T::operator()(std::cw<&s>, std::cw<8>);
+    static_assert(result == 50);
+  }
+  {
+    // overload set
+    // will always unwrap the constexpr params and call the non-constexpr overload
+    using T                                  = std::constant_wrapper<OverloadSet{}>;
+    std::same_as<int> decltype(auto) result1 = T::operator()(42);
+    assert(result1 == 1);
+    std::same_as<std::constant_wrapper<1>> decltype(auto) result2 = T::operator()(std::cw<42>);
+    static_assert(result2 == 1);
+  }
+
+  {
+    // return non-structural type
+    using T                                           = std::constant_wrapper<ReturnNonStructural{}>;
+    std::same_as<NonStructural> decltype(auto) result = T::operator()(5);
+    assert(result.get() == 5);
+  }
+
+  {
+    // return non-structural type with constexpr param
+    using T                                           = std::constant_wrapper<ReturnNonStructural{}>;
+    std::same_as<NonStructural> decltype(auto) result = T::operator()(std::cw<5>);
+    assert(result.get() == 5);
+  }
+
+  {
+    // cw only
+    // the upwrapping case doesn't work so it falls back to the normal invoke path
+    using T                                 = std::constant_wrapper<CWOnly{}>;
+    std::same_as<int> decltype(auto) result = T::operator()(std::cw<42>);
+    assert(result == 42);
+  }
+
+  {
+    // just use the call operator
+    assert(std::cw<[](int i) { return i + 1; }>(42) == 43);
+    assert(std::cw<[](int i) { return i + 1; }>(std::cw<42>) == 43);
+  }
+
+  {
+    // with integral_constant, will still call the constexpr path
+    using T = std::constant_wrapper<std::plus<>{}>;
+    std::integral_constant<int, 1> ic1;
+    std::integral_constant<int, 2> ic2;
+    std::same_as<std::constant_wrapper<3>> decltype(auto) result = T::operator()(ic1, ic2);
+    static_assert(result == 3);
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}
diff --git a/libcxx/test/std/utilities/const.wrap.class/comma.pass.cpp b/libcxx/test/std/utilities/const.wrap.class/comma.pass.cpp
new file mode 100644
index 0000000000000..42c8f74cf76f2
--- /dev/null
+++ b/libcxx/test/std/utilities/const.wrap.class/comma.pass.cpp
@@ -0,0 +1,66 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+// ADDITIONAL_COMPILE_FLAGS: -Wno-unused-value
+
+// constant_wrapper
+
+// template<constexpr-param L, constexpr-param R>
+//   friend constexpr auto operator,(L, R) noexcept = delete;
+
+#include <cassert>
+#include <concepts>
+#include <functional>
+#include <type_traits>
+#include <utility>
+
+#include "test_macros.h"
+
+struct WithOps {
+  int value;
+
+  constexpr WithOps(int v) : value(v) {}
+
+  friend constexpr auto operator,(const WithOps& /*l*/, WithOps r) { return WithOps{r.value}; }
+};
+
+struct NoOps {};
+
+template <class L, class R>
+concept HasComma = requires(L l, R r) {
+  { l, r };
+};
+
+// Comma operator is deleted for constant_wrapper operands
+static_assert(!HasComma<std::constant_wrapper<6>, std::constant_wrapper<3>>);
+static_assert(!HasComma<std::constant_wrapper<WithOps{6}>, std::constant_wrapper<WithOps{3}>>);
+static_assert(!HasComma<std::constant_wrapper<NoOps{}>, std::constant_wrapper<NoOps{}>>);
+
+// Mixed operands - one constant_wrapper, one runtime type (uses built-in operator)
+static_assert(HasComma<std::constant_wrapper<42>, int>);
+static_assert(HasComma<int, std::constant_wrapper<42>>);
+
+constexpr bool test() {
+  {
+    // only mixed with runtime parameters
+    std::constant_wrapper<42> cw42;
+    int i                                     = 0;
+    std::same_as<int&> decltype(auto) result1 = (cw42, i);
+    assert(result1 == 0);
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}
diff --git a/libcxx/test/std/utilities/const.wrap.class/comp.pass.cpp b/libcxx/test/std/utilities/const.wrap.class/comp.pass.cpp
new file mode 100644
index 0000000000000..d18ae25bfb050
--- /dev/null
+++ b/libcxx/test/std/utilities/const.wrap.class/comp.pass.cpp
@@ -0,0 +1,424 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+
+// constant_wrapper
+
+// template<constexpr-param L, constexpr-param R>
+//   friend constexpr auto operator<=>(L, R) noexcept
+//     -> constant_wrapper<(L::value <=> R::value)>
+//       { return {}; }
+// template<constexpr-param L, constexpr-param R>
+//   friend constexpr auto operator<(L, R) noexcept -> constant_wrapper<(L::value < R::value)>
+//     { return {}; }
+// template<constexpr-param L, constexpr-param R>
+//   friend constexpr auto operator<=(L, R) noexcept -> constant_wrapper<(L::value <= R::value)>
+//     { return {}; }
+// template<constexpr-param L, constexpr-param R>
+//   friend constexpr auto operator==(L, R) noexcept -> constant_wrapper<(L::value == R::value)>
+//     { return {}; }
+// template<constexpr-param L, constexpr-param R>
+//   friend constexpr auto operator!=(L, R) noexcept -> constant_wrapper<(L::value != R::value)>
+//     { return {}; }
+// template<constexpr-param L, constexpr-param R>
+//   friend constexpr auto operator>(L, R) noexcept -> constant_wrapper<(L::value > R::value)>
+//     { return {}; }
+// template<constexpr-param L, constexpr-param R>
+//   friend constexpr auto operator>=(L, R) noexcept -> constant_wrapper<(L::value >= R::value)>
+//     { return {}; }
+
+#include <cassert>
+#include <compare>
+#include <concepts>
+#include <functional>
+#include <type_traits>
+#include <utility>
+
+#include "helpers.h"
+#include "test_macros.h"
+
+struct WithOps {
+  int value;
+
+  constexpr WithOps(int v) : value(v) {}
+
+  friend constexpr auto operator==(WithOps l, WithOps r) { return l.value == r.value; }
+  friend constexpr auto operator!=(WithOps l, WithOps r) { return l.value != r.value; }
+  friend constexpr auto operator<(WithOps l, WithOps r) { return l.value < r.value; }
+  friend constexpr auto operator<=(WithOps l, WithOps r) { return l.value <= r.value; }
+  friend constexpr auto operator>=(WithOps l, WithOps r) { return l.value >= r.value; }
+  friend constexpr auto operator>(WithOps l, WithOps r) { return l.value > r.value; }
+  friend constexpr auto operator<=>(WithOps l, WithOps r) { return l.value <=> r.value; }
+};
+
+struct OptsReturnNonStructural {
+  int value;
+
+  constexpr OptsReturnNonStructural(int v) : value(v) {}
+
+  friend constexpr auto operator==(OptsReturnNonStructural l, OptsReturnNonStructural r) {
+    return NonStructural{l.value == r.value ? 1 : 0};
+  }
+  friend constexpr auto operator!=(OptsReturnNonStructural l, OptsReturnNonStructural r) {
+    return NonStructural{l.value != r.value ? 1 : 0};
+  }
+  friend constexpr auto operator<(OptsReturnNonStructural l, OptsReturnNonStructural r) {
+    return NonStructural{l.value < r.value ? 1 : 0};
+  }
+  friend constexpr auto operator<=(OptsReturnNonStructural l, OptsReturnNonStructural r) {
+    return NonStructural{l.value <= r.value ? 1 : 0};
+  }
+  friend constexpr auto operator>=(OptsReturnNonStructural l, OptsReturnNonStructural r) {
+    return NonStructural{l.value >= r.value ? 1 : 0};
+  }
+  friend constexpr auto operator>(OptsReturnNonStructural l, OptsReturnNonStructural r) {
+    return NonStructural{l.value > r.value ? 1 : 0};
+  }
+  friend constexpr auto operator<=>(OptsReturnNonStructural l, OptsReturnNonStructural r) {
+    return NonStructural{(l.value < r.value) ? -1 : (l.value > r.value) ? 1 : 0};
+  }
+};
+
+struct NoOps {};
+
+template <class L, class R>
+concept HasEqual = requires(L l, R r) {
+  { l == r };
+};
+
+template <class L, class R>
+concept HasNotEqual = requires(L l, R r) {
+  { l != r };
+};
+
+template <class L, class R>
+concept HasLess = requires(L l, R r) {
+  { l < r };
+};
+
+template <class L, class R>
+concept HasLessEqual = requires(L l, R r) {
+  { l <= r };
+};
+
+template <class L, class R>
+concept HasGreater = requires(L l, R r) {
+  { l > r };
+};
+
+template <class L, class R>
+concept HasGreaterEqual = requires(L l, R r) {
+  { l >= r };
+};
+
+template <class L, class R>
+concept HasSpaceship = requires(L l, R r) {
+  { l <=> r };
+};
+
+template <class L, class R>
+concept HasNoexceptEqual = requires(L l, R r) {
+  { l == r } noexcept;
+};
+
+template <class L, class R>
+concept HasNoexceptNotEqual = requires(L l, R r) {
+  { l != r } noexcept;
+};
+
+template <class L, class R>
+concept HasNoexceptLess = requires(L l, R r) {
+  { l < r } noexcept;
+};
+
+template <class L, class R>
+concept HasNoexceptLessEqual = requires(L l, R r) {
+  { l <= r } noexcept;
+};
+
+template <class L, class R>
+concept HasNoexceptGreater = requires(L l, R r) {
+  { l > r } noexcept;
+};
+
+template <class L, class R>
+concept HasNoexceptGreaterEqual = requires(L l, R r) {
+  { l >= r } noexcept;
+};
+
+template <class L, class R>
+concept HasNoexceptSpaceship = requires(L l, R r) {
+  { l <=> r } noexcept;
+};
+
+// Concept checks for int comparisons
+static_assert(HasEqual<std::constant_wrapper<6>, std::constant_wrapper<3>>);
+static_assert(HasNotEqual<std::constant_wrapper<6>, std::constant_wrapper<3>>);
+static_assert(HasLess<std::constant_wrapper<6>, std::constant_wrapper<3>>);
+static_assert(HasLessEqual<std::constant_wrapper<6>, std::constant_wrapper<3>>);
+static_assert(HasGreater<std::constant_wrapper<6>, std::constant_wrapper<3>>);
+static_assert(HasGreaterEqual<std::constant_wrapper<6>, std::constant_wrapper<3>>);
+static_assert(HasSpaceship<std::constant_wrapper<6>, std::constant_wrapper<3>>);
+
+static_assert(HasNoexceptEqual<std::constant_wrapper<6>, std::constant_wrapper<3>>);
+static_assert(HasNoexceptNotEqual<std::constant_wrapper<6>, std::constant_wrapper<3>>);
+static_assert(HasNoexceptLess<std::constant_wrapper<6>, std::constant_wrapper<3>>);
+static_assert(HasNoexceptLessEqual<std::constant_wrapper<6>, std::constant_wrapper<3>>);
+static_assert(HasNoexceptGreater<std::constant_wrapper<6>, std::constant_wrapper<3>>);
+static_assert(HasNoexceptGreaterEqual<std::constant_wrapper<6>, std::constant_wrapper<3>>);
+static_assert(HasNoexceptSpaceship<std::constant_wrapper<6>, std::constant_wrapper<3>>);
+
+// NoOps
+static_assert(!HasEqual<std::constant_wrapper<NoOps{}>, std::constant_wrapper<NoOps{}>>);
+static_assert(!HasNotEqual<std::constant_wrapper<NoOps{}>, std::constant_wrapper<NoOps{}>>);
+static_assert(!HasLess<std::constant_wrapper<NoOps{}>, std::constant_wrapper<NoOps{}>>);
+static_assert(!HasLessEqual<std::constant_wrapper<NoOps{}>, std::constant_wrapper<NoOps{}>>);
+static_assert(!HasGreater<std::constant_wrapper<NoOps{}>, std::constant_wrapper<NoOps{}>>);
+static_assert(!HasGreaterEqual<std::constant_wrapper<NoOps{}>, std::constant_wrapper<NoOps{}>>);
+static_assert(!HasSpaceship<std::constant_wrapper<NoOps{}>, std::constant_wrapper<NoOps{}>>);
+
+// Concept checks for WithOps comparisons
+static_assert(HasNoexceptEqual<std::constant_wrapper<WithOps{6}>, std::constant_wrapper<WithOps{3}>>);
+static_assert(HasNoexceptNotEqual<std::constant_wrapper<WithOps{6}>, std::constant_wrapper<WithOps{3}>>);
+static_assert(HasNoexceptLess<std::constant_wrapper<WithOps{6}>, std::constant_wrapper<WithOps{3}>>);
+static_assert(HasNoexceptLessEqual<std::constant_wrapper<WithOps{6}>, std::constant_wrapper<WithOps{3}>>);
+static_assert(HasNoexceptGreater<std::constant_wrapper<WithOps{6}>, std::constant_wrapper<WithOps{3}>>);
+static_assert(HasNoexceptGreaterEqual<std::constant_wrapper<WithOps{6}>, std::constant_wrapper<WithOps{3}>>);
+static_assert(!HasNoexceptSpaceship<std::constant_wrapper<WithOps{6}>, std::constant_wrapper<WithOps{3}>>,
+              "strong_ordering is not a structural type, so the call falls back to runtime implicit conversion and "
+              "operator<=>, which is noexcept(false)");
+
+// Non-structural types use implicit conversion to underlying type
+static_assert(
+    HasEqual<std::constant_wrapper<OptsReturnNonStructural{6}>, std::constant_wrapper<OptsReturnNonStructural{3}>>);
+static_assert(
+    HasNotEqual<std::constant_wrapper<OptsReturnNonStructural{6}>, std::constant_wrapper<OptsReturnNonStructural{3}>>);
+static_assert(
+    HasLess<std::constant_wrapper<OptsReturnNonStructural{6}>, std::constant_wrapper<OptsReturnNonStructural{3}>>);
+static_assert(
+    HasLessEqual<std::constant_wrapper<OptsReturnNonStructural{6}>, std::constant_wrapper<OptsReturnNonStructural{3}>>);
+static_assert(
+    HasGreater<std::constant_wrapper<OptsReturnNonStructural{6}>, std::constant_wrapper<OptsReturnNonStructural{3}>>);
+static_assert(HasGreaterEqual<std::constant_wrapper<OptsReturnNonStructural{6}>,
+                              std::constant_wrapper<OptsReturnNonStructural{3}>>);
+
+static_assert(!HasNoexceptEqual<std::constant_wrapper<OptsReturnNonStructural{6}>,
+                                std::constant_wrapper<OptsReturnNonStructural{3}>>);
+static_assert(!HasNoexceptNotEqual<std::constant_wrapper<OptsReturnNonStructural{6}>,
+                                   std::constant_wrapper<OptsReturnNonStructural{3}>>);
+static_assert(!HasNoexceptLess<std::constant_wrapper<OptsReturnNonStructural{6}>,
+                               std::constant_wrapper<OptsReturnNonStructural{3}>>);
+static_assert(!HasNoexceptLessEqual<std::constant_wrapper<OptsReturnNonStructural{6}>,
+                                    std::constant_wrapper<OptsReturnNonStructural{3}>>);
+static_assert(!HasNoexceptGreater<std::constant_wrapper<OptsReturnNonStructural{6}>,
+                                  std::constant_wrapper<OptsReturnNonStructural{3}>>);
+static_assert(!HasNoexceptGreaterEqual<std::constant_wrapper<OptsReturnNonStructural{6}>,
+                                       std::constant_wrapper<OptsReturnNonStructural{3}>>);
+
+constexpr bool test() {
+  {
+    // int comparisons: 6 vs 3 - returns constant_wrapper<bool_value>
+    std::constant_wrapper<6> cw6;
+    std::constant_wrapper<3> cw3;
+
+    std::same_as<std::constant_wrapper<false>> decltype(auto) equal = cw6 == cw3;
+    static_assert(!static_cast<bool>(equal));
+
+    std::same_as<std::constant_wrapper<true>> decltype(auto) not_equal = cw6 != cw3;
+    static_assert(static_cast<bool>(not_equal));
+
+    std::same_as<std::constant_wrapper<false>> decltype(auto) less = cw6 < cw3;
+    static_assert(!static_cast<bool>(less));
+
+    std::same_as<std::constant_wrapper<false>> decltype(auto) less_equal = cw6 <= cw3;
+    static_assert(!static_cast<bool>(less_equal));
+
+    std::same_as<std::constant_wrapper<true>> decltype(auto) greater = cw6 > cw3;
+    static_assert(static_cast<bool>(greater));
+
+    std::same_as<std::constant_wrapper<true>> decltype(auto) greater_equal = cw6 >= cw3;
+    static_assert(static_cast<bool>(greater_equal));
+
+    // strong_ordering is not a structural type
+    std::same_as<std::strong_ordering> decltype(auto) spaceship = cw6 <=> cw3;
+    assert(spaceship == std::strong_ordering::greater);
+  }
+
+  {
+    // int comparisons: equal values
+    std::constant_wrapper<3> cw3a;
+    std::constant_wrapper<3> cw3b;
+
+    std::same_as<std::constant_wrapper<true>> decltype(auto) equal = cw3a == cw3b;
+    static_assert(static_cast<bool>(equal));
+
+    std::same_as<std::constant_wrapper<false>> decltype(auto) not_equal = cw3a != cw3b;
+    static_assert(!static_cast<bool>(not_equal));
+
+    std::same_as<std::constant_wrapper<false>> decltype(auto) less = cw3a < cw3b;
+    static_assert(!static_cast<bool>(less));
+
+    std::same_as<std::constant_wrapper<true>> decltype(auto) less_equal = cw3a <= cw3b;
+    static_assert(static_cast<bool>(less_equal));
+
+    std::same_as<std::constant_wrapper<true>> decltype(auto) greater = cw3a >= cw3b;
+    static_assert(static_cast<bool>(greater));
+
+    std::same_as<std::constant_wrapper<false>> decltype(auto) greater_cmp = cw3a > cw3b;
+    static_assert(!static_cast<bool>(greater_cmp));
+
+    std::same_as<std::strong_ordering> decltype(auto) spaceship = cw3a <=> cw3b;
+    assert(spaceship == std::strong_ordering::equal);
+  }
+
+  {
+    // WithOps comparisons - returns constant_wrapper<bool_value>
+    std::constant_wrapper<WithOps{6}> cwWithOps6;
+    std::constant_wrapper<WithOps{3}> cwWithOps3;
+
+    std::same_as<std::constant_wrapper<false>> decltype(auto) equal = cwWithOps6 == cwWithOps3;
+    static_assert(!static_cast<bool>(equal));
+
+    std::same_as<std::constant_wrapper<true>> decltype(auto) not_equal = cwWithOps6 != cwWithOps3;
+    static_assert(static_cast<bool>(not_equal));
+
+    std::same_as<std::constant_wrapper<false>> decltype(auto) less = cwWithOps6 < cwWithOps3;
+    static_assert(!static_cast<bool>(less));
+
+    std::same_as<std::constant_wrapper<false>> decltype(auto) less_equal = cwWithOps6 <= cwWithOps3;
+    static_assert(!static_cast<bool>(less_equal));
+
+    std::same_as<std::constant_wrapper<true>> decltype(auto) greater = cwWithOps6 > cwWithOps3;
+    static_assert(static_cast<bool>(greater));
+
+    std::same_as<std::constant_wrapper<true>> decltype(auto) greater_equal = cwWithOps6 >= cwWithOps3;
+    static_assert(static_cast<bool>(greater_equal));
+
+    std::same_as<std::strong_ordering> decltype(auto) spaceship = cwWithOps6 <=> cwWithOps3;
+    assert(spaceship == std::strong_ordering::greater);
+  }
+
+  {
+    // WithOps comparisons: equal values
+    std::constant_wrapper<WithOps{3}> cwWithOps3a;
+    std::constant_wrapper<WithOps{3}> cwWithOps3b;
+
+    std::same_as<std::constant_wrapper<true>> decltype(auto) equal = cwWithOps3a == cwWithOps3b;
+    static_assert(static_cast<bool>(equal));
+
+    std::same_as<std::constant_wrapper<false>> decltype(auto) not_equal = cwWithOps3a != cwWithOps3b;
+    static_assert(!static_cast<bool>(not_equal));
+
+    std::same_as<std::constant_wrapper<false>> decltype(auto) less = cwWithOps3a < cwWithOps3b;
+    static_assert(!static_cast<bool>(less));
+
+    std::same_as<std::constant_wrapper<true>> decltype(auto) less_equal = cwWithOps3a <= cwWithOps3b;
+    static_assert(static_cast<bool>(less_equal));
+
+    std::same_as<std::constant_wrapper<true>> decltype(auto) greater_equal = cwWithOps3a >= cwWithOps3b;
+    static_assert(static_cast<bool>(greater_equal));
+
+    std::same_as<std::constant_wrapper<false>> decltype(auto) greater = cwWithOps3a > cwWithOps3b;
+    static_assert(!static_cast<bool>(greater));
+
+    std::same_as<std::strong_ordering> decltype(auto) spaceship = cwWithOps3a <=> cwWithOps3b;
+    assert(spaceship == std::strong_ordering::equal);
+  }
+
+  {
+    // Non-structural return types use implicit conversion
+    std::constant_wrapper<OptsReturnNonStructural{6}> cwOpt6;
+    std::constant_wrapper<OptsReturnNonStructural{3}> cwOpt3;
+
+    std::same_as<NonStructural> decltype(auto) equal = cwOpt6 == cwOpt3;
+    assert(equal.get() == 0);
+
+    std::same_as<NonStructural> decltype(auto) not_equal = cwOpt6 != cwOpt3;
+    assert(not_equal.get() == 1);
+
+    std::same_as<NonStructural> decltype(auto) less = cwOpt6 < cwOpt3;
+    assert(less.get() == 0);
+
+    std::same_as<NonStructural> decltype(auto) less_equal = cwOpt6 <= cwOpt3;
+    assert(less_equal.get() == 0);
+
+    std::same_as<NonStructural> decltype(auto) greater = cwOpt6 > cwOpt3;
+    assert(greater.get() == 1);
+
+    std::same_as<NonStructural> decltype(auto) greater_equal = cwOpt6 >= cwOpt3;
+    assert(greater_equal.get() == 1);
+
+    std::same_as<NonStructural> decltype(auto) spaceship = cwOpt6 <=> cwOpt3;
+    assert(spaceship.get() == 1);
+  }
+
+  {
+    // Mix with runtime param: these operators are not used (built-in operators)
+    std::constant_wrapper<6> cw6;
+    int i = 3;
+
+    std::same_as<bool> decltype(auto) equal = cw6 == i;
+    assert(!equal);
+
+    std::same_as<bool> decltype(auto) not_equal = cw6 != i;
+    assert(not_equal);
+
+    std::same_as<bool> decltype(auto) less = cw6 < i;
+    assert(!less);
+
+    std::same_as<bool> decltype(auto) less_equal = cw6 <= i;
+    assert(!less_equal);
+
+    std::same_as<bool> decltype(auto) greater = cw6 > i;
+    assert(greater);
+
+    std::same_as<bool> decltype(auto) greater_equal = cw6 >= i;
+    assert(greater_equal);
+
+    std::same_as<std::strong_ordering> decltype(auto) spaceship = cw6 <=> i;
+    assert(spaceship == std::strong_ordering::greater);
+  }
+
+  {
+    // with integral_constant
+    std::constant_wrapper<6> cw6;
+    std::integral_constant<int, 3> ic3;
+
+    std::same_as<std::constant_wrapper<false>> decltype(auto) equal = cw6 == ic3;
+    static_assert(!static_cast<bool>(equal));
+
+    std::same_as<std::constant_wrapper<true>> decltype(auto) not_equal = cw6 != ic3;
+    static_assert(static_cast<bool>(not_equal));
+
+    std::same_as<std::constant_wrapper<false>> decltype(auto) less = cw6 < ic3;
+    static_assert(!static_cast<bool>(less));
+
+    std::same_as<std::constant_wrapper<false>> decltype(auto) less_equal = cw6 <= ic3;
+    static_assert(!static_cast<bool>(less_equal));
+
+    std::same_as<std::constant_wrapper<true>> decltype(auto) greater = cw6 > ic3;
+    static_assert(static_cast<bool>(greater));
+
+    std::same_as<std::constant_wrapper<true>> decltype(auto) greater_equal = cw6 >= ic3;
+    static_assert(static_cast<bool>(greater_equal));
+
+    // strong_ordering is not a structural type
+    std::same_as<std::strong_ordering> decltype(auto) spaceship = cw6 <=> ic3;
+    assert(spaceship == std::strong_ordering::greater);
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}
diff --git a/libcxx/test/std/utilities/const.wrap.class/convert.pass.cpp b/libcxx/test/std/utilities/const.wrap.class/convert.pass.cpp
new file mode 100644
index 0000000000000..1261a1426f23d
--- /dev/null
+++ b/libcxx/test/std/utilities/const.wrap.class/convert.pass.cpp
@@ -0,0 +1,90 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+
+// constant_wrapper
+
+// constexpr operator decltype(auto)() const noexcept { return value; }
+
+#include <cassert>
+#include <concepts>
+#include <type_traits>
+#include <utility>
+
+#include "helpers.h"
+#include "test_macros.h"
+
+struct S {
+  int value;
+
+  constexpr S(int v) : value(v) {}
+};
+
+constexpr void f1(const S&) {}
+
+constexpr bool test() {
+  {
+    // int conversion
+    std::constant_wrapper<6> cw6;
+    const int& result = cw6;
+    assert(result == 6);
+    assert(&result == &cw6.value);
+
+    static_assert(noexcept(static_cast<const int&>(cw6)));
+  }
+
+  {
+    // struct conversion
+    constexpr S s{42};
+    std::constant_wrapper<s> cws;
+    const S& result = cws;
+    assert(result.value == 42);
+    assert(&result == &cws.value);
+
+    static_assert(noexcept(static_cast<const S&>(cws)));
+  }
+
+  {
+    // array conversion
+    constexpr int arr[] = {1, 2, 3};
+    std::constant_wrapper<arr> cwArr;
+    const int (&result)[3] = cwArr;
+    assert(result[0] == 1);
+    assert(result[1] == 2);
+    assert(result[2] == 3);
+    assert(&result == &cwArr.value);
+
+    static_assert(noexcept(static_cast<const int (&)[3]>(cwArr)));
+  }
+
+  {
+    // function pointer conversion
+    constexpr int (*fptr)(int) = [](int x) constexpr { return x * 2; };
+    std::constant_wrapper<fptr> cwFptr;
+    int (*result)(int) = cwFptr;
+    assert(result(5) == 10);
+
+    static_assert(noexcept(static_cast<int (*)(int)>(cwFptr)));
+  }
+
+  {
+    // conversion is implicit
+    std::constant_wrapper<S{42}> cws;
+    f1(cws);
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}
diff --git a/libcxx/test/std/utilities/const.wrap.class/ctad.compile.pass.cpp b/libcxx/test/std/utilities/const.wrap.class/ctad.compile.pass.cpp
new file mode 100644
index 0000000000000..b8a2bdead9396
--- /dev/null
+++ b/libcxx/test/std/utilities/const.wrap.class/ctad.compile.pass.cpp
@@ -0,0 +1,37 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+
+// constant_wrapper
+
+// template<class T, size_t Extent>
+//   cw-fixed-value(T (&)[Extent]) -> cw-fixed-value<T[Extent]>;                   // exposition only
+
+#include <cassert>
+#include <concepts>
+#include <type_traits>
+#include <utility>
+
+#include "helpers.h"
+#include "test_macros.h"
+
+constexpr int arr[] = {1, 2, 3};
+using T1            = std::constant_wrapper<arr>;
+static_assert(std::is_same_v<T1::value_type, const int[3]>);
+
+using T2 = std::constant_wrapper<"hello world">;
+static_assert(std::is_same_v<T2::value_type, const char[12]>);
+
+struct S {
+  int value;
+};
+
+constexpr S s[] = {{1}, {2}, {3}};
+using T3        = std::constant_wrapper<s>;
+static_assert(std::is_same_v<T3::value_type, const S[3]>);
diff --git a/libcxx/test/std/utilities/const.wrap.class/cw.pass.cpp b/libcxx/test/std/utilities/const.wrap.class/cw.pass.cpp
new file mode 100644
index 0000000000000..6dd5154a03f32
--- /dev/null
+++ b/libcxx/test/std/utilities/const.wrap.class/cw.pass.cpp
@@ -0,0 +1,74 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+
+// constant_wrapper
+
+//   template<cw-fixed-value X>
+//    constexpr auto cw = constant_wrapper<X>{};
+
+#include <cassert>
+#include <concepts>
+#include <type_traits>
+#include <utility>
+
+#include "helpers.h"
+#include "test_macros.h"
+
+struct S {
+  int value;
+
+  constexpr S(int v) : value(v) {}
+
+  constexpr bool operator==(const S& other) const { return value == other.value; }
+};
+
+constexpr bool test() {
+  {
+    // int constant
+    std::same_as<const std::constant_wrapper<42>> decltype(auto) cw_val = std::cw<42>;
+    static_assert(cw_val == 42);
+  }
+
+  {
+    // struct constant
+    constexpr S s{13};
+    std::same_as<const std::constant_wrapper<s>> decltype(auto) cw_val = std::cw<s>;
+    static_assert(cw_val == s);
+  }
+
+  {
+    // array constant
+    constexpr int arr[]                                                  = {1, 2, 3};
+    std::same_as<const std::constant_wrapper<arr>> decltype(auto) cw_val = std::cw<arr>;
+    static_assert(cw_val[0] == 1);
+    static_assert(cw_val[1] == 2);
+    static_assert(cw_val[2] == 3);
+  }
+
+  {
+    // string literals
+    std::same_as<const std::constant_wrapper<"hello">> decltype(auto) cw_val = std::cw<"hello">;
+    static_assert(cw_val[0] == 'h');
+    static_assert(cw_val[1] == 'e');
+    static_assert(cw_val[2] == 'l');
+    static_assert(cw_val[3] == 'l');
+    static_assert(cw_val[4] == 'o');
+    static_assert(cw_val[5] == '\0');
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}
diff --git a/libcxx/test/std/utilities/const.wrap.class/cw_fixed.array.ctor.pass.cpp b/libcxx/test/std/utilities/const.wrap.class/cw_fixed.array.ctor.pass.cpp
new file mode 100644
index 0000000000000..735e5d3f0d4bb
--- /dev/null
+++ b/libcxx/test/std/utilities/const.wrap.class/cw_fixed.array.ctor.pass.cpp
@@ -0,0 +1,91 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+
+// constant_wrapper
+
+// constexpr cw-fixed-value(T (&arr)[Extent]) noexcept;
+
+#include <cassert>
+#include <concepts>
+#include <type_traits>
+#include <utility>
+
+#include "helpers.h"
+#include "test_macros.h"
+
+template <auto v>
+auto helper(std::constant_wrapper<v>) -> decltype(v);
+
+template <class T>
+using cw_fixed_value = decltype(helper(std::constant_wrapper<T{}>{}));
+
+struct S {
+  int value;
+
+  constexpr S(int v = 0) : value(v) {}
+
+  constexpr bool operator==(const S& other) const { return value == other.value; }
+};
+
+constexpr bool test() {
+  {
+    // int array construction
+    // the conversion from int array to cw-fixed-value<int array> uses the constructor
+    constexpr int arr[] = {1, 2, 3};
+    std::constant_wrapper<arr> cw{};
+    assert(cw.value[0] == 1);
+    assert(cw.value[1] == 2);
+    assert(cw.value[2] == 3);
+  }
+
+  {
+    // struct array construction
+    constexpr S s[] = {{1}, {2}, {3}};
+    std::constant_wrapper<s> cw{};
+    assert(cw.value[0] == S{1});
+    assert(cw.value[1] == S{2});
+    assert(cw.value[2] == S{3});
+  }
+
+  {
+    // calling the constructor
+    constexpr int arr[] = {1, 2, 3, 4, 5};
+    constexpr cw_fixed_value<const int[5]> ci(arr);
+    std::constant_wrapper<ci> cw;
+    assert(cw.value[0] == 1);
+    assert(cw.value[1] == 2);
+    assert(cw.value[2] == 3);
+    assert(cw.value[3] == 4);
+    assert(cw.value[4] == 5);
+
+    static_assert(noexcept(cw_fixed_value<const int[5]>{arr}));
+  }
+
+  {
+    // the constructor is implicit
+    constexpr int arr[]                       = {1, 2, 3, 4, 5};
+    constexpr cw_fixed_value<const int[5]> ci = arr;
+    std::constant_wrapper<ci> cw;
+    assert(cw.value[0] == 1);
+    assert(cw.value[1] == 2);
+    assert(cw.value[2] == 3);
+    assert(cw.value[3] == 4);
+    assert(cw.value[4] == 5);
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}
diff --git a/libcxx/test/std/utilities/const.wrap.class/cw_fixed.ctor.pass.cpp b/libcxx/test/std/utilities/const.wrap.class/cw_fixed.ctor.pass.cpp
new file mode 100644
index 0000000000000..ebed00153eab5
--- /dev/null
+++ b/libcxx/test/std/utilities/const.wrap.class/cw_fixed.ctor.pass.cpp
@@ -0,0 +1,75 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+
+// constant_wrapper
+
+// constexpr cw-fixed-value(type v) noexcept : data(v) {}
+
+#include <cassert>
+#include <concepts>
+#include <type_traits>
+#include <utility>
+
+#include "helpers.h"
+#include "test_macros.h"
+
+template <auto v>
+auto helper(std::constant_wrapper<v>) -> decltype(v);
+
+template <class T>
+using cw_fixed_value = decltype(helper(std::constant_wrapper<T{}>{}));
+
+struct S {
+  int value;
+
+  constexpr S(int v = 0) : value(v) {}
+
+  constexpr bool operator==(const S& other) const { return value == other.value; }
+};
+
+constexpr bool test() {
+  {
+    // int construction
+    // the conversion from int to cw-fixed-value<int> uses the constructor
+    std::constant_wrapper<42> cw{};
+    assert(cw.value == 42);
+  }
+
+  {
+    // struct construction
+    std::constant_wrapper<S{13}> cw{};
+    assert(cw.value == S{13});
+  }
+
+  {
+    // calling the constructor
+    constexpr cw_fixed_value<int> ci{42};
+    std::constant_wrapper<ci> cw;
+    assert(cw == 42);
+
+    static_assert(noexcept(cw_fixed_value<int>{42}));
+  }
+
+  {
+    // the constructor is implicit
+    constexpr cw_fixed_value<int> ci = 42;
+    std::constant_wrapper<ci> cw;
+    assert(cw == 42);
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}
diff --git a/libcxx/test/std/utilities/const.wrap.class/general.pass.cpp b/libcxx/test/std/utilities/const.wrap.class/general.pass.cpp
new file mode 100644
index 0000000000000..754b4f377f588
--- /dev/null
+++ b/libcxx/test/std/utilities/const.wrap.class/general.pass.cpp
@@ -0,0 +1,47 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+
+// constant_wrapper
+
+// The class template constant_wrapper aids in metaprogramming by ensuring that the
+// evaluation of expressions comprised entirely of constant_wrapper are core constant
+// expressions ([expr.const]), regardless of the context in which they appear. In particular,
+// this enables use of constant_wrapper values that are passed as arguments to constexpr
+// functions to be used in constant expressions.
+
+#include <cassert>
+#include <concepts>
+#include <type_traits>
+#include <utility>
+#include <iostream>
+
+#include "helpers.h"
+#include "test_macros.h"
+
+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);
+}
+
+int main(int, char**) {
+  impeccable_underground_planning();
+  return 0;
+}
diff --git a/libcxx/test/std/utilities/const.wrap.class/helpers.h b/libcxx/test/std/utilities/const.wrap.class/helpers.h
new file mode 100644
index 0000000000000..f94d604731331
--- /dev/null
+++ b/libcxx/test/std/utilities/const.wrap.class/helpers.h
@@ -0,0 +1,23 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef TEST_STD_UTILITIES_CONST_WRAP_CLASS_HELPERS_H
+#define TEST_STD_UTILITIES_CONST_WRAP_CLASS_HELPERS_H
+
+
+struct NonStructural {
+  constexpr NonStructural(int i) : value(i) {}
+
+  constexpr int get() const { return value; }
+
+private:
+  int value;
+};
+
+
+#endif // TEST_STD_UTILITIES_CONST_WRAP_CLASS_HELPERS_H
diff --git a/libcxx/test/std/utilities/const.wrap.class/mem_ptr.pass.cpp b/libcxx/test/std/utilities/const.wrap.class/mem_ptr.pass.cpp
new file mode 100644
index 0000000000000..ff33dda6cebbf
--- /dev/null
+++ b/libcxx/test/std/utilities/const.wrap.class/mem_ptr.pass.cpp
@@ -0,0 +1,116 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+// ADDITIONAL_COMPILE_FLAGS: -Wno-unused-value
+
+// constant_wrapper
+
+// template<constexpr-param L, constexpr-param R>
+//   friend constexpr auto operator->*(L, R) noexcept -> constant_wrapper<L::value->*(R::value)>
+//     { return {}; }
+
+#include <cassert>
+#include <concepts>
+#include <type_traits>
+#include <utility>
+
+#include "helpers.h"
+#include "test_macros.h"
+
+struct S {
+  int member = 42;
+};
+
+constexpr S s;
+
+template <class L, class R>
+concept HasPtrToMem = requires(L l, R r) {
+  { l->*r };
+};
+
+template <class L, class R>
+concept HasNoexceptPtrToMem = requires(L l, R r) {
+  { l->*r } noexcept;
+};
+
+struct WithOps {
+  int value;
+  constexpr WithOps(int v) : value(v) {}
+
+  friend constexpr auto operator->*(WithOps w, int WithOps::* pm) { return w.value + (&w)->*pm; }
+};
+
+struct OptsReturnNonStructural {
+  int value;
+  constexpr OptsReturnNonStructural(int v) : value(v) {}
+
+  friend constexpr auto operator->*(OptsReturnNonStructural o, int OptsReturnNonStructural::* pm) {
+    return NonStructural{o.value + (&o)->*pm};
+  }
+};
+
+struct NoOps {};
+
+static_assert(HasPtrToMem<std::constant_wrapper<&s>, std::constant_wrapper<&S::member>>);
+static_assert(HasNoexceptPtrToMem<std::constant_wrapper<&s>, std::constant_wrapper<&S::member>>);
+
+static_assert(HasPtrToMem<std::constant_wrapper<&s>, int S::*>);
+static_assert(!HasPtrToMem<std::constant_wrapper<&s>, int>);
+
+constexpr bool test() {
+  {
+    // use builtin operator->*
+    std::constant_wrapper<(&s)> cwS;
+    std::constant_wrapper<&S::member> cwPM;
+    std::same_as<std::constant_wrapper<42>> decltype(auto) result1 = cwS->*cwPM;
+    static_assert(result1 == 42);
+  }
+
+  {
+    // mix runtime and constant_wrapper parameters, will use built-in operator
+    std::constant_wrapper<(&s)> cwS;
+    int S::* pm                                     = &S::member;
+    std::same_as<const int&> decltype(auto) result1 = cwS->*pm;
+    assert(result1 == 42);
+  }
+
+  {
+    // custom operator->*
+    std::constant_wrapper<WithOps{42}> cwWO;
+    std::constant_wrapper<&WithOps::value> cwPM;
+    std::same_as<std::constant_wrapper<84>> decltype(auto) result1 = cwWO->*cwPM;
+    static_assert(result1 == 84);
+  }
+
+  {
+    // Return non-structural type
+    // Will use underlying type's runtime operators
+    std::constant_wrapper<OptsReturnNonStructural{42}> cwORNS;
+    std::constant_wrapper<&OptsReturnNonStructural::value> cwPM;
+    std::same_as<NonStructural> decltype(auto) result1 = cwORNS->*cwPM;
+    assert(result1.get() == 84);
+  }
+
+  {
+    // integral_constant
+    std::constant_wrapper<(&s)> cwS;
+    std::integral_constant<int S::*, &S::member> icPM;
+    std::same_as<std::constant_wrapper<42>> decltype(auto) result1 = cwS->*icPM;
+    static_assert(result1 == 42);
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}
diff --git a/libcxx/test/std/utilities/const.wrap.class/pseudo_mutators.pass.cpp b/libcxx/test/std/utilities/const.wrap.class/pseudo_mutators.pass.cpp
new file mode 100644
index 0000000000000..100dca90d4601
--- /dev/null
+++ b/libcxx/test/std/utilities/const.wrap.class/pseudo_mutators.pass.cpp
@@ -0,0 +1,427 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+
+// constant_wrapper pseudo-mutators
+
+// template<constexpr-param T>
+//   constexpr auto operator++(this T) noexcept
+//     -> constant_wrapper<++(T::value)> { return {}; }
+// template<constexpr-param T>
+//   constexpr auto operator++(this T, int) noexcept
+//     -> constant_wrapper<(T::value++)> { return {}; }
+// template<constexpr-param T>
+//   constexpr auto operator--(this T) noexcept
+//     -> constant_wrapper<--(T::value)> { return {}; }
+// template<constexpr-param T>
+//   constexpr auto operator--(this T, int) noexcept
+//     -> constant_wrapper<(T::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<constexpr-param T, constexpr-param R>
+//   constexpr auto operator>>=(this T, R) noexcept
+//     -> constant_wrapper<(T::value >>= R::value)> { return {}; }
+
+#include <cassert>
+#include <concepts>
+#include <functional>
+#include <type_traits>
+#include <utility>
+
+#include "helpers.h"
+#include "test_macros.h"
+
+struct WithOps {
+  int value;
+
+  constexpr WithOps(int v) : value(v) {}
+
+  constexpr auto operator++() const { return WithOps{value + 1}; }
+  constexpr auto operator++(int) const { return WithOps{value + 1}; }
+  constexpr auto operator--() const { return WithOps{value - 1}; }
+  constexpr auto operator--(int) const { return WithOps{value - 1}; }
+
+  constexpr auto operator+=(WithOps r) const { return WithOps{value + r.value}; }
+  constexpr auto operator-=(WithOps r) const { return WithOps{value - r.value}; }
+  constexpr auto operator*=(WithOps r) const { return WithOps{value * r.value}; }
+  constexpr auto operator/=(WithOps r) const { return WithOps{value / r.value}; }
+  constexpr auto operator%=(WithOps r) const { return WithOps{value % r.value}; }
+  constexpr auto operator&=(WithOps r) const { return WithOps{value & r.value}; }
+  constexpr auto operator|=(WithOps r) const { return WithOps{value | r.value}; }
+  constexpr auto operator^=(WithOps r) const { return WithOps{value ^ r.value}; }
+  constexpr auto operator<<=(WithOps r) const { return WithOps{value << r.value}; }
+  constexpr auto operator>>=(WithOps r) const { return WithOps{value >> r.value}; }
+};
+
+struct OptsReturnNonStructural {
+  int value;
+
+  constexpr OptsReturnNonStructural(int v) : value(v) {}
+
+  constexpr auto operator++() const { return NonStructural{value + 1}; }
+  constexpr auto operator++(int) const { return NonStructural{value + 1}; }
+  constexpr auto operator--() const { return NonStructural{value - 1}; }
+  constexpr auto operator--(int) const { return NonStructural{value - 1}; }
+
+  constexpr auto operator+=(OptsReturnNonStructural r) const { return NonStructural{value + r.value}; }
+  constexpr auto operator-=(OptsReturnNonStructural r) const { return NonStructural{value - r.value}; }
+  constexpr auto operator*=(OptsReturnNonStructural r) const { return NonStructural{value * r.value}; }
+  constexpr auto operator/=(OptsReturnNonStructural r) const { return NonStructural{value / r.value}; }
+  constexpr auto operator%=(OptsReturnNonStructural r) const { return NonStructural{value % r.value}; }
+  constexpr auto operator&=(OptsReturnNonStructural r) const { return NonStructural{value & r.value}; }
+  constexpr auto operator|=(OptsReturnNonStructural r) const { return NonStructural{value | r.value}; }
+  constexpr auto operator^=(OptsReturnNonStructural r) const { return NonStructural{value ^ r.value}; }
+  constexpr auto operator<<=(OptsReturnNonStructural r) const { return NonStructural{value << r.value}; }
+  constexpr auto operator>>=(OptsReturnNonStructural r) const { return NonStructural{value >> r.value}; }
+};
+
+struct NoOps {};
+
+template <class T>
+concept HasPreIncrement = requires(T t) {
+  { ++t };
+};
+
+template <class T>
+concept HasPostIncrement = requires(T t) {
+  { t++ };
+};
+
+template <class T>
+concept HasPreDecrement = requires(T t) {
+  { --t };
+};
+
+template <class T>
+concept HasPostDecrement = requires(T t) {
+  { t-- };
+};
+
+template <class L, class R>
+concept HasPlusAssign = requires(L l, R r) {
+  { l += r };
+};
+
+template <class L, class R>
+concept HasMinusAssign = requires(L l, R r) {
+  { l -= r };
+};
+
+template <class L, class R>
+concept HasMultiplyAssign = requires(L l, R r) {
+  { l *= r };
+};
+
+template <class L, class R>
+concept HasDivideAssign = requires(L l, R r) {
+  { l /= r };
+};
+
+template <class L, class R>
+concept HasModuloAssign = requires(L l, R r) {
+  { l %= r };
+};
+
+template <class L, class R>
+concept HasBitAndAssign = requires(L l, R r) {
+  { l &= r };
+};
+
+template <class L, class R>
+concept HasBitOrAssign = requires(L l, R r) {
+  { l |= r };
+};
+
+template <class L, class R>
+concept HasBitXorAssign = requires(L l, R r) {
+  { l ^= r };
+};
+
+template <class L, class R>
+concept HasShiftLeftAssign = requires(L l, R r) {
+  { l <<= r };
+};
+
+template <class L, class R>
+concept HasShiftRightAssign = requires(L l, R r) {
+  { l >>= r };
+};
+
+template <class T>
+concept HasNoexceptPreIncrement = requires(T t) {
+  { ++t } noexcept;
+};
+
+template <class T>
+concept HasNoexceptPostIncrement = requires(T t) {
+  { t++ } noexcept;
+};
+
+template <class T>
+concept HasNoexceptPreDecrement = requires(T t) {
+  { --t } noexcept;
+};
+
+template <class T>
+concept HasNoexceptPostDecrement = requires(T t) {
+  { t-- } noexcept;
+};
+
+template <class L, class R>
+concept HasNoexceptPlusAssign = requires(L l, R r) {
+  { l += r } noexcept;
+};
+
+template <class L, class R>
+concept HasNoexceptMinusAssign = requires(L l, R r) {
+  { l -= r } noexcept;
+};
+
+template <class L, class R>
+concept HasNoexceptMultiplyAssign = requires(L l, R r) {
+  { l *= r } noexcept;
+};
+
+template <class L, class R>
+concept HasNoexceptDivideAssign = requires(L l, R r) {
+  { l /= r } noexcept;
+};
+
+template <class L, class R>
+concept HasNoexceptModuloAssign = requires(L l, R r) {
+  { l %= r } noexcept;
+};
+
+template <class L, class R>
+concept HasNoexceptBitAndAssign = requires(L l, R r) {
+  { l &= r } noexcept;
+};
+
+template <class L, class R>
+concept HasNoexceptBitOrAssign = requires(L l, R r) {
+  { l |= r } noexcept;
+};
+
+template <class L, class R>
+concept HasNoexceptBitXorAssign = requires(L l, R r) {
+  { l ^= r } noexcept;
+};
+
+template <class L, class R>
+concept HasNoexceptShiftLeftAssign = requires(L l, R r) {
+  { l <<= r } noexcept;
+};
+
+template <class L, class R>
+concept HasNoexceptShiftRightAssign = requires(L l, R r) {
+  { l >>= r } noexcept;
+};
+
+// Pseudo-mutators does work with int as built-in types mutating operators are const
+static_assert(!HasPreIncrement<std::constant_wrapper<6>>);
+static_assert(!HasPostIncrement<std::constant_wrapper<6>>);
+static_assert(!HasPreDecrement<std::constant_wrapper<6>>);
+static_assert(!HasPostDecrement<std::constant_wrapper<6>>);
+
+static_assert(!HasPlusAssign<std::constant_wrapper<6>, std::constant_wrapper<3>>);
+static_assert(!HasMinusAssign<std::constant_wrapper<6>, std::constant_wrapper<3>>);
+static_assert(!HasMultiplyAssign<std::constant_wrapper<6>, std::constant_wrapper<3>>);
+static_assert(!HasDivideAssign<std::constant_wrapper<6>, std::constant_wrapper<3>>);
+static_assert(!HasModuloAssign<std::constant_wrapper<6>, std::constant_wrapper<3>>);
+static_assert(!HasBitAndAssign<std::constant_wrapper<6>, std::constant_wrapper<3>>);
+static_assert(!HasBitOrAssign<std::constant_wrapper<6>, std::constant_wrapper<3>>);
+static_assert(!HasBitXorAssign<std::constant_wrapper<6>, std::constant_wrapper<3>>);
+static_assert(!HasShiftLeftAssign<std::constant_wrapper<6>, std::constant_wrapper<1>>);
+static_assert(!HasShiftRightAssign<std::constant_wrapper<6>, std::constant_wrapper<1>>);
+
+// NoOps - pseudo-mutators shouldn't work without supporting operators
+static_assert(!HasPreIncrement<std::constant_wrapper<NoOps{}>>);
+static_assert(!HasPostIncrement<std::constant_wrapper<NoOps{}>>);
+static_assert(!HasPreDecrement<std::constant_wrapper<NoOps{}>>);
+static_assert(!HasPostDecrement<std::constant_wrapper<NoOps{}>>);
+
+static_assert(!HasPlusAssign<std::constant_wrapper<NoOps{}>, std::constant_wrapper<NoOps{}>>);
+static_assert(!HasMinusAssign<std::constant_wrapper<NoOps{}>, std::constant_wrapper<NoOps{}>>);
+static_assert(!HasMultiplyAssign<std::constant_wrapper<NoOps{}>, std::constant_wrapper<NoOps{}>>);
+static_assert(!HasDivideAssign<std::constant_wrapper<NoOps{}>, std::constant_wrapper<NoOps{}>>);
+static_assert(!HasModuloAssign<std::constant_wrapper<NoOps{}>, std::constant_wrapper<NoOps{}>>);
+static_assert(!HasBitAndAssign<std::constant_wrapper<NoOps{}>, std::constant_wrapper<NoOps{}>>);
+static_assert(!HasBitOrAssign<std::constant_wrapper<NoOps{}>, std::constant_wrapper<NoOps{}>>);
+static_assert(!HasBitXorAssign<std::constant_wrapper<NoOps{}>, std::constant_wrapper<NoOps{}>>);
+static_assert(!HasShiftLeftAssign<std::constant_wrapper<NoOps{}>, std::constant_wrapper<NoOps{}>>);
+static_assert(!HasShiftRightAssign<std::constant_wrapper<NoOps{}>, std::constant_wrapper<NoOps{}>>);
+
+// Pseudo-mutators work with WithOps types
+static_assert(HasNoexceptPreIncrement<std::constant_wrapper<WithOps{6}>>);
+static_assert(HasNoexceptPostIncrement<std::constant_wrapper<WithOps{6}>>);
+static_assert(HasNoexceptPreDecrement<std::constant_wrapper<WithOps{6}>>);
+static_assert(HasNoexceptPostDecrement<std::constant_wrapper<WithOps{6}>>);
+
+static_assert(HasNoexceptPlusAssign<std::constant_wrapper<WithOps{6}>, std::constant_wrapper<WithOps{3}>>);
+static_assert(HasNoexceptMinusAssign<std::constant_wrapper<WithOps{6}>, std::constant_wrapper<WithOps{3}>>);
+static_assert(HasNoexceptMultiplyAssign<std::constant_wrapper<WithOps{6}>, std::constant_wrapper<WithOps{3}>>);
+static_assert(HasNoexceptDivideAssign<std::constant_wrapper<WithOps{6}>, std::constant_wrapper<WithOps{3}>>);
+static_assert(HasNoexceptModuloAssign<std::constant_wrapper<WithOps{6}>, std::constant_wrapper<WithOps{3}>>);
+static_assert(HasNoexceptBitAndAssign<std::constant_wrapper<WithOps{6}>, std::constant_wrapper<WithOps{3}>>);
+static_assert(HasNoexceptBitOrAssign<std::constant_wrapper<WithOps{6}>, std::constant_wrapper<WithOps{3}>>);
+static_assert(HasNoexceptBitXorAssign<std::constant_wrapper<WithOps{6}>, std::constant_wrapper<WithOps{3}>>);
+static_assert(HasNoexceptShiftLeftAssign<std::constant_wrapper<WithOps{6}>, std::constant_wrapper<WithOps{1}>>);
+static_assert(HasNoexceptShiftRightAssign<std::constant_wrapper<WithOps{6}>, std::constant_wrapper<WithOps{1}>>);
+
+// Non-structural return types cannot use implicit conversions too because they are member functions and cannot be found through ADL
+static_assert(!HasPreIncrement<std::constant_wrapper<OptsReturnNonStructural{6}>>);
+static_assert(!HasPostIncrement<std::constant_wrapper<OptsReturnNonStructural{6}>>);
+static_assert(!HasPreDecrement<std::constant_wrapper<OptsReturnNonStructural{6}>>);
+static_assert(!HasPostDecrement<std::constant_wrapper<OptsReturnNonStructural{6}>>);
+
+static_assert(!HasPlusAssign<std::constant_wrapper<OptsReturnNonStructural{6}>,
+                             std::constant_wrapper<OptsReturnNonStructural{3}>>);
+static_assert(!HasMinusAssign<std::constant_wrapper<OptsReturnNonStructural{6}>,
+                              std::constant_wrapper<OptsReturnNonStructural{3}>>);
+static_assert(!HasMultiplyAssign<std::constant_wrapper<OptsReturnNonStructural{6}>,
+                                 std::constant_wrapper<OptsReturnNonStructural{3}>>);
+static_assert(!HasDivideAssign<std::constant_wrapper<OptsReturnNonStructural{6}>,
+                               std::constant_wrapper<OptsReturnNonStructural{3}>>);
+static_assert(!HasModuloAssign<std::constant_wrapper<OptsReturnNonStructural{6}>,
+                               std::constant_wrapper<OptsReturnNonStructural{3}>>);
+static_assert(!HasBitAndAssign<std::constant_wrapper<OptsReturnNonStructural{6}>,
+                               std::constant_wrapper<OptsReturnNonStructural{3}>>);
+static_assert(!HasBitOrAssign<std::constant_wrapper<OptsReturnNonStructural{6}>,
+                              std::constant_wrapper<OptsReturnNonStructural{3}>>);
+static_assert(!HasBitXorAssign<std::constant_wrapper<OptsReturnNonStructural{6}>,
+                               std::constant_wrapper<OptsReturnNonStructural{3}>>);
+static_assert(!HasShiftLeftAssign<std::constant_wrapper<OptsReturnNonStructural{6}>,
+                                  std::constant_wrapper<OptsReturnNonStructural{1}>>);
+static_assert(!HasShiftRightAssign<std::constant_wrapper<OptsReturnNonStructural{6}>,
+                                   std::constant_wrapper<OptsReturnNonStructural{1}>>);
+
+constexpr bool test() {
+  {
+    // WithOps increment/decrement
+    std::constant_wrapper<WithOps{5}> cwWithOps5;
+    std::same_as<std::constant_wrapper<WithOps{6}>> decltype(auto) result1 = ++cwWithOps5;
+    static_assert(result1.value.value == 6);
+
+    std::same_as<std::constant_wrapper<WithOps{6}>> decltype(auto) result2 = cwWithOps5++;
+    static_assert(result2.value.value == 6);
+
+    std::same_as<std::constant_wrapper<WithOps{4}>> decltype(auto) result3 = --cwWithOps5;
+    static_assert(result3.value.value == 4);
+
+    std::same_as<std::constant_wrapper<WithOps{4}>> decltype(auto) result4 = cwWithOps5--;
+    static_assert(result4.value.value == 4);
+  }
+
+  {
+    // WithOps compound assignments
+    std::constant_wrapper<WithOps{10}> cwWithOps10;
+    std::constant_wrapper<WithOps{3}> cwWithOps3;
+
+    std::same_as<std::constant_wrapper<WithOps{13}>> decltype(auto) result1 = cwWithOps10 += cwWithOps3;
+    static_assert(result1.value.value == 13);
+
+    std::same_as<std::constant_wrapper<WithOps{7}>> decltype(auto) result2 = cwWithOps10 -= cwWithOps3;
+    static_assert(result2.value.value == 7);
+
+    std::same_as<std::constant_wrapper<WithOps{30}>> decltype(auto) result3 = cwWithOps10 *= cwWithOps3;
+    static_assert(result3.value.value == 30);
+
+    std::same_as<std::constant_wrapper<WithOps{3}>> decltype(auto) result4 = cwWithOps10 /= cwWithOps3;
+    static_assert(result4.value.value == 3);
+
+    std::same_as<std::constant_wrapper<WithOps{1}>> decltype(auto) result5 = cwWithOps10 %= cwWithOps3;
+    static_assert(result5.value.value == 1);
+
+    std::same_as<std::constant_wrapper<WithOps{2}>> decltype(auto) result6 = cwWithOps10 &= cwWithOps3;
+    static_assert(result6.value.value == 2);
+
+    std::same_as<std::constant_wrapper<WithOps{11}>> decltype(auto) result7 = cwWithOps10 |= cwWithOps3;
+    static_assert(result7.value.value == 11);
+
+    std::same_as<std::constant_wrapper<WithOps{9}>> decltype(auto) result8 = cwWithOps10 ^= cwWithOps3;
+    static_assert(result8.value.value == 9);
+
+    std::same_as<std::constant_wrapper<WithOps{80}>> decltype(auto) result9 = cwWithOps10 <<= cwWithOps3;
+    static_assert(result9.value.value == 80);
+
+    std::same_as<std::constant_wrapper<WithOps{1}>> decltype(auto) result10 = cwWithOps10 >>= cwWithOps3;
+    static_assert(result10.value.value == 1);
+  }
+
+  {
+    // integral_constant compound assignments
+    std::constant_wrapper<WithOps{10}> cwWithOps10;
+    std::integral_constant<WithOps, WithOps{3}> icWithOps3;
+
+    std::same_as<std::constant_wrapper<WithOps{13}>> decltype(auto) result1 = cwWithOps10 += icWithOps3;
+    static_assert(result1.value.value == 13);
+
+    std::same_as<std::constant_wrapper<WithOps{7}>> decltype(auto) result2 = cwWithOps10 -= icWithOps3;
+    static_assert(result2.value.value == 7);
+
+    std::same_as<std::constant_wrapper<WithOps{30}>> decltype(auto) result3 = cwWithOps10 *= icWithOps3;
+    static_assert(result3.value.value == 30);
+
+    std::same_as<std::constant_wrapper<WithOps{3}>> decltype(auto) result4 = cwWithOps10 /= icWithOps3;
+    static_assert(result4.value.value == 3);
+
+    std::same_as<std::constant_wrapper<WithOps{1}>> decltype(auto) result5 = cwWithOps10 %= icWithOps3;
+    static_assert(result5.value.value == 1);
+
+    std::same_as<std::constant_wrapper<WithOps{2}>> decltype(auto) result6 = cwWithOps10 &= icWithOps3;
+    static_assert(result6.value.value == 2);
+
+    std::same_as<std::constant_wrapper<WithOps{11}>> decltype(auto) result7 = cwWithOps10 |= icWithOps3;
+    static_assert(result7.value.value == 11);
+
+    std::same_as<std::constant_wrapper<WithOps{9}>> decltype(auto) result8 = cwWithOps10 ^= icWithOps3;
+    static_assert(result8.value.value == 9);
+
+    std::same_as<std::constant_wrapper<WithOps{80}>> decltype(auto) result9 = cwWithOps10 <<= icWithOps3;
+    static_assert(result9.value.value == 80);
+
+    std::same_as<std::constant_wrapper<WithOps{1}>> decltype(auto) result10 = cwWithOps10 >>= icWithOps3;
+    static_assert(result10.value.value == 1);
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}
diff --git a/libcxx/test/std/utilities/const.wrap.class/subscript.pass.cpp b/libcxx/test/std/utilities/const.wrap.class/subscript.pass.cpp
new file mode 100644
index 0000000000000..c81811ea5e1a8
--- /dev/null
+++ b/libcxx/test/std/utilities/const.wrap.class/subscript.pass.cpp
@@ -0,0 +1,187 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+
+// constant_wrapper
+
+// template<class... Args>
+// static constexpr decltype(auto) operator[](Args&&... args) noexcept(see below);
+
+#include <array>
+#include <cassert>
+#include <concepts>
+#include <functional>
+#include <type_traits>
+#include <utility>
+
+#include "helpers.h"
+#include "MoveOnly.h"
+#include "test_macros.h"
+
+struct MoveOnlyIndex {
+  constexpr MoveOnly operator[](const MoveOnly& m1, MoveOnly m2, MoveOnly&& m3) const {
+    return MoveOnly(m1.get() + m2.get() + m3.get());
+  }
+};
+
+struct Nary {
+  constexpr int operator[](auto... args) const { return sizeof...(args); }
+};
+
+struct OverloadSet {
+  constexpr int operator[](int) const { return 1; }
+
+  constexpr int operator[](std::constant_wrapper<42>) const { return 2; }
+};
+
+struct ReturnNonStructural {
+  constexpr NonStructural operator[](int i) const { return NonStructural{i}; }
+};
+
+struct CWOnly {
+  constexpr int operator[](std::constant_wrapper<42>) const { return 42; }
+};
+
+struct ThrowingSubscript {
+  constexpr int operator[](int) const { return 42; }
+};
+
+struct NothrowSubscript {
+  constexpr int operator[](int) const noexcept { return 42; }
+};
+
+constexpr int arr[] = {1, 2, 3, 4};
+
+// Let subscr-expr be constant_wrapper<value[remove_cvref_t<Args>::value...]>{} if all types in remove_cvref_t<Args>... satisfy constexpr-param and constant_wrapper<value[remove_cvref_t<Args>::value...]>
+// is a valid type, otherwise let subscr-expr be value[std::forward<Args>(args)...].
+// - Constraints: subscr-expr is a valid expression.
+// - Remarks: The exception specification is equivalent to noexcept(subscr-expr).
+
+template <class T, class... Args>
+concept HasSubscript = requires(T t, Args&&... args) {
+  { t[std::forward<Args>(args)...] };
+};
+
+template <class T, class... Args>
+concept HasNothrowSubscript = requires(T t, Args&&... args) {
+  { t[std::forward<Args>(args)...] } noexcept;
+};
+
+static_assert(!HasSubscript<std::constant_wrapper<4>, std::constant_wrapper<1>>);
+
+static_assert(HasSubscript<std::constant_wrapper<arr>, int>);
+static_assert(HasSubscript<std::constant_wrapper<arr>, std::constant_wrapper<1>>);
+
+static_assert(HasNothrowSubscript<std::constant_wrapper<arr>, int>);
+static_assert(HasNothrowSubscript<std::constant_wrapper<arr>, std::constant_wrapper<1>>);
+
+static_assert(HasSubscript<std::constant_wrapper<NothrowSubscript{}>, int>);
+static_assert(HasNothrowSubscript<std::constant_wrapper<NothrowSubscript{}>, int>);
+
+static_assert(HasSubscript<std::constant_wrapper<ThrowingSubscript{}>, int>);
+static_assert(!HasNothrowSubscript<std::constant_wrapper<ThrowingSubscript{}>, int>);
+static_assert(HasNothrowSubscript<std::constant_wrapper<ThrowingSubscript{}>, std::constant_wrapper<1>>,
+              "the subscript expression is still nothrow because the constexpr path is taken");
+
+constexpr bool test() {
+  {
+    // with runtime param
+    using T                                        = std::constant_wrapper<arr>;
+    std::same_as<const int&> decltype(auto) result = T::operator[](1);
+    assert(result == 2);
+  }
+  {
+    // with constexpr param
+    using T                                                      = std::constant_wrapper<arr>;
+    std::same_as<std::constant_wrapper<2>> decltype(auto) result = T::operator[](std::cw<1>);
+    static_assert(result == 2);
+  }
+
+  {
+    // null-ary
+    using T                                                      = std::constant_wrapper<Nary{}>;
+    std::same_as<std::constant_wrapper<0>> decltype(auto) result = T::operator[]();
+    static_assert(result == 0);
+  }
+
+  {
+    // n-ary
+    using T                                                      = std::constant_wrapper<Nary{}>;
+    std::same_as<std::constant_wrapper<3>> decltype(auto) result = T::operator[](std::cw<1>, std::cw<2>, std::cw<3>);
+    static_assert(result == 3);
+  }
+
+  {
+    // mixing constexpr and runtime
+    using T                                 = std::constant_wrapper<Nary{}>;
+    std::same_as<int> decltype(auto) result = T::operator[](std::cw<1>, 2, std::cw<3>);
+    assert(result == 3);
+  }
+
+  {
+    // move only
+    using T = std::constant_wrapper<MoveOnlyIndex{}>;
+    MoveOnly m1(1), m2(2), m3(3);
+    std::same_as<MoveOnly> decltype(auto) result = T::operator[](m1, std::move(m2), std::move(m3));
+    assert(result.get() == 6);
+  }
+  {
+    // overload set
+    // will always unwrap the constexpr params and call the non-constexpr overload
+    using T                                  = std::constant_wrapper<OverloadSet{}>;
+    std::same_as<int> decltype(auto) result1 = T::operator[](42);
+    assert(result1 == 1);
+    std::same_as<std::constant_wrapper<1>> decltype(auto) result2 = T::operator[](std::cw<42>);
+    static_assert(result2 == 1);
+  }
+
+  {
+    // return non-structural type
+    using T                                           = std::constant_wrapper<ReturnNonStructural{}>;
+    std::same_as<NonStructural> decltype(auto) result = T::operator[](5);
+    assert(result.get() == 5);
+  }
+
+  {
+    // return non-structural type with constexpr param
+    using T                                           = std::constant_wrapper<ReturnNonStructural{}>;
+    std::same_as<NonStructural> decltype(auto) result = T::operator[](std::cw<5>);
+    assert(result.get() == 5);
+  }
+
+  {
+    // cw only
+    // the upwrapping case doesn't work so it falls back to the normal invoke path
+    using T                                 = std::constant_wrapper<CWOnly{}>;
+    std::same_as<int> decltype(auto) result = T::operator[](std::cw<42>);
+    assert(result == 42);
+  }
+
+  {
+    // just use the index operator
+    assert(std::cw<"abcd">[2] == 'c');
+    assert(std::cw<"abcd">[std::cw<3>] == 'd');
+  }
+
+  {
+    // integral_constant
+    using T                                                      = std::constant_wrapper<arr>;
+    std::same_as<std::constant_wrapper<2>> decltype(auto) result = T::operator[](std::integral_constant<int, 1>{});
+    static_assert(result == 2);
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}
diff --git a/libcxx/test/std/utilities/const.wrap.class/types.compile.pass.cpp b/libcxx/test/std/utilities/const.wrap.class/types.compile.pass.cpp
new file mode 100644
index 0000000000000..312b312c6019c
--- /dev/null
+++ b/libcxx/test/std/utilities/const.wrap.class/types.compile.pass.cpp
@@ -0,0 +1,40 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+
+// constant_wrapper
+
+// static constexpr const auto & value = X.data;
+// using type = constant_wrapper;
+// using value_type = decltype(X)::type;
+
+#include <algorithm>
+#include <cassert>
+#include <concepts>
+#include <type_traits>
+#include <utility>
+
+static_assert(std::constant_wrapper<42>::value == 42);
+static_assert(std::same_as<decltype(std::constant_wrapper<42>::value), const int&>);
+static_assert(std::same_as<std::constant_wrapper<42>::type, std::constant_wrapper<42>>);
+static_assert(std::same_as<std::constant_wrapper<42>::value_type, int>);
+
+struct S {
+  int member = 42;
+};
+
+static_assert(std::constant_wrapper<S{5}>::value.member == 5);
+static_assert(std::same_as<decltype(std::constant_wrapper<S{5}>::value), const S&>);
+static_assert(std::same_as<std::constant_wrapper<S{5}>::type, std::constant_wrapper<S{5}>>);
+static_assert(std::same_as<std::constant_wrapper<S{5}>::value_type, S>);
+
+static_assert(std::ranges::equal(std::constant_wrapper<"abcd">::value, "abcd"));
+static_assert(std::same_as<decltype(std::constant_wrapper<"abcd">::value), const char (&)[5]>);
+static_assert(std::same_as<std::constant_wrapper<"abcd">::type, std::constant_wrapper<"abcd">>);
+static_assert(std::same_as<std::constant_wrapper<"abcd">::value_type, const char[5]>);
diff --git a/libcxx/test/std/utilities/const.wrap.class/unary_ops.pass.cpp b/libcxx/test/std/utilities/const.wrap.class/unary_ops.pass.cpp
new file mode 100644
index 0000000000000..343f7a6c8760f
--- /dev/null
+++ b/libcxx/test/std/utilities/const.wrap.class/unary_ops.pass.cpp
@@ -0,0 +1,249 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+
+// constant_wrapper
+
+//  template<constexpr-param T>
+//    friend constexpr auto operator+(T) noexcept -> constant_wrapper<(+T::value)>
+//      { return {}; }
+//  template<constexpr-param T>
+//    friend constexpr auto operator-(T) noexcept -> constant_wrapper<(-T::value)>
+//      { return {}; }
+//  template<constexpr-param T>
+//    friend constexpr auto operator~(T) noexcept -> constant_wrapper<(~T::value)>
+//      { return {}; }
+//  template<constexpr-param T>
+//    friend constexpr auto operator!(T) noexcept -> constant_wrapper<(!T::value)>
+//      { return {}; }
+//  template<constexpr-param T>
+//    friend constexpr auto operator&(T) noexcept -> constant_wrapper<(&T::value)>
+//      { return {}; }
+//  template<constexpr-param T>
+//    friend constexpr auto operator*(T) noexcept -> constant_wrapper<(*T::value)>
+//      { return {}; }
+
+#include <cassert>
+#include <concepts>
+#include <functional>
+#include <type_traits>
+#include <utility>
+
+#include "helpers.h"
+#include "test_macros.h"
+
+struct WithOps {
+  int value;
+
+  constexpr WithOps(int v) : value(v) {}
+
+  friend constexpr auto operator+(WithOps w) { return WithOps{+w.value}; }
+  friend constexpr auto operator-(WithOps w) { return WithOps{-w.value}; }
+  friend constexpr auto operator~(WithOps w) { return WithOps{~w.value}; }
+  friend constexpr auto operator!(WithOps w) { return WithOps{!w.value}; }
+  friend constexpr auto operator&(WithOps w) { return WithOps{w.value + 42}; }
+  friend constexpr auto operator*(WithOps w) { return WithOps{w.value - 42}; }
+};
+
+struct OptsReturnNonStructural {
+  int value;
+
+  constexpr OptsReturnNonStructural(int v) : value(v) {}
+
+  friend constexpr auto operator+(OptsReturnNonStructural o) { return NonStructural{+o.value}; }
+  friend constexpr auto operator-(OptsReturnNonStructural o) { return NonStructural{-o.value}; }
+  friend constexpr auto operator~(OptsReturnNonStructural o) { return NonStructural{~o.value}; }
+  friend constexpr auto operator!(OptsReturnNonStructural o) { return NonStructural{!o.value}; }
+  friend constexpr auto operator&(OptsReturnNonStructural o) { return NonStructural{o.value + 42}; }
+  friend constexpr auto operator*(OptsReturnNonStructural o) { return NonStructural{o.value - 42}; }
+};
+
+struct NoOps {};
+
+template <class T>
+concept HasPlus = requires(T t) {
+  { +t };
+};
+
+template <class T>
+concept HasMinus = requires(T t) {
+  { -t };
+};
+
+template <class T>
+concept HasBitNot = requires(T t) {
+  { ~t };
+};
+
+template <class T>
+concept HasNot = requires(T t) {
+  { !t };
+};
+
+template <class T>
+concept HasBitAnd = requires(T t) {
+  { &t };
+};
+
+template <class T>
+concept HasDeref = requires(T t) {
+  { *t };
+};
+
+template <class T>
+concept HasNoexceptPlus = requires(T t) {
+  { +t } noexcept;
+};
+
+template <class T>
+concept HasNoexceptMinus = requires(T t) {
+  { -t } noexcept;
+};
+
+template <class T>
+concept HasNoexceptBitNot = requires(T t) {
+  { ~t } noexcept;
+};
+
+template <class T>
+concept HasNoexceptNot = requires(T t) {
+  { !t } noexcept;
+};
+
+template <class T>
+concept HasNoexceptBitAnd = requires(T t) {
+  { &t } noexcept;
+};
+
+template <class T>
+concept HasNoexceptDeref = requires(T t) {
+  { *t } noexcept;
+};
+
+static_assert(HasPlus<std::constant_wrapper<WithOps{42}>>);
+static_assert(HasMinus<std::constant_wrapper<WithOps{42}>>);
+static_assert(HasBitNot<std::constant_wrapper<WithOps{42}>>);
+static_assert(HasNot<std::constant_wrapper<WithOps{42}>>);
+static_assert(HasBitAnd<std::constant_wrapper<WithOps{42}>>);
+static_assert(HasDeref<std::constant_wrapper<WithOps{42}>>);
+
+static_assert(HasNoexceptPlus<std::constant_wrapper<WithOps{42}>>);
+static_assert(HasNoexceptMinus<std::constant_wrapper<WithOps{42}>>);
+static_assert(HasNoexceptBitNot<std::constant_wrapper<WithOps{42}>>);
+static_assert(HasNoexceptNot<std::constant_wrapper<WithOps{42}>>);
+static_assert(HasNoexceptBitAnd<std::constant_wrapper<WithOps{42}>>);
+static_assert(HasNoexceptDeref<std::constant_wrapper<WithOps{42}>>);
+
+static_assert(HasNoexceptPlus<std::constant_wrapper<42>>);
+static_assert(HasNoexceptMinus<std::constant_wrapper<42>>);
+static_assert(HasNoexceptBitNot<std::constant_wrapper<42>>);
+static_assert(HasNoexceptNot<std::constant_wrapper<42>>);
+static_assert(HasNoexceptBitAnd<std::constant_wrapper<42>>);
+static_assert(!HasDeref<std::constant_wrapper<42>>);
+
+static_assert(!HasPlus<std::constant_wrapper<NoOps{}>>);
+static_assert(!HasMinus<std::constant_wrapper<NoOps{}>>);
+static_assert(!HasBitNot<std::constant_wrapper<NoOps{}>>);
+static_assert(!HasNot<std::constant_wrapper<NoOps{}>>);
+static_assert(HasBitAnd<std::constant_wrapper<NoOps{}>>);
+static_assert(!HasDeref<std::constant_wrapper<NoOps{}>>);
+
+// The operators from constant_wrapper do not exist, but they can be implicited converted
+// to the underlying type and use its operators instead.
+static_assert(HasPlus<std::constant_wrapper<OptsReturnNonStructural{42}>>);
+static_assert(HasMinus<std::constant_wrapper<OptsReturnNonStructural{42}>>);
+static_assert(HasBitNot<std::constant_wrapper<OptsReturnNonStructural{42}>>);
+static_assert(HasNot<std::constant_wrapper<OptsReturnNonStructural{42}>>);
+static_assert(HasBitAnd<std::constant_wrapper<OptsReturnNonStructural{42}>>);
+static_assert(HasDeref<std::constant_wrapper<OptsReturnNonStructural{42}>>);
+
+static_assert(!HasNoexceptPlus<std::constant_wrapper<OptsReturnNonStructural{42}>>);
+static_assert(!HasNoexceptMinus<std::constant_wrapper<OptsReturnNonStructural{42}>>);
+static_assert(!HasNoexceptBitNot<std::constant_wrapper<OptsReturnNonStructural{42}>>);
+static_assert(!HasNoexceptNot<std::constant_wrapper<OptsReturnNonStructural{42}>>);
+static_assert(!HasNoexceptBitAnd<std::constant_wrapper<OptsReturnNonStructural{42}>>);
+static_assert(!HasNoexceptDeref<std::constant_wrapper<OptsReturnNonStructural{42}>>);
+
+constexpr bool test() {
+  {
+    // int
+    std::constant_wrapper<42> cw42;
+
+    std::same_as<std::constant_wrapper<42>> decltype(auto) result = +cw42;
+    static_assert(result == 42);
+
+    std::same_as<std::constant_wrapper<-42>> decltype(auto) result2 = -cw42;
+    static_assert(result2 == -42);
+
+    std::same_as<std::constant_wrapper<~42>> decltype(auto) result3 = ~cw42;
+    static_assert(result3 == ~42);
+
+    std::same_as<std::constant_wrapper<!42>> decltype(auto) result4 = !cw42;
+    static_assert(result4 == !42);
+
+    std::same_as<std::constant_wrapper<&cw42.value>> decltype(auto) result5 = &cw42;
+    static_assert(result5 == &cw42.value);
+  }
+
+  {
+    // WithOps
+    std::constant_wrapper<WithOps{42}> cwWithOps;
+
+    std::same_as<std::constant_wrapper<WithOps{42}>> decltype(auto) result = +cwWithOps;
+    static_assert(result.value.value == 42);
+
+    std::same_as<std::constant_wrapper<WithOps{-42}>> decltype(auto) result2 = -cwWithOps;
+    static_assert(result2.value.value == -42);
+
+    std::same_as<std::constant_wrapper<WithOps{~42}>> decltype(auto) result3 = ~cwWithOps;
+    static_assert(result3.value.value == ~42);
+
+    std::same_as<std::constant_wrapper<WithOps{!42}>> decltype(auto) result4 = !cwWithOps;
+    static_assert(result4.value.value == !42);
+
+    std::same_as<std::constant_wrapper<WithOps{84}>> decltype(auto) result5 = &cwWithOps;
+    static_assert(result5.value.value == 84);
+
+    std::same_as<std::constant_wrapper<WithOps{0}>> decltype(auto) result6 = *cwWithOps;
+    static_assert(result6.value.value == 0);
+  }
+
+  {
+    // Return non-structural type
+    // Will use underlying type's runtime operators
+    std::constant_wrapper<OptsReturnNonStructural{42}> cwOptsReturnNonStructural;
+
+    std::same_as<NonStructural> decltype(auto) result = +cwOptsReturnNonStructural;
+    assert(result.get() == 42);
+
+    std::same_as<NonStructural> decltype(auto) result2 = -cwOptsReturnNonStructural;
+    assert(result2.get() == -42);
+
+    std::same_as<NonStructural> decltype(auto) result3 = ~cwOptsReturnNonStructural;
+    assert(result3.get() == ~42);
+
+    std::same_as<NonStructural> decltype(auto) result4 = !cwOptsReturnNonStructural;
+    assert(result4.get() == !42);
+
+    std::same_as<NonStructural> decltype(auto) result5 = &cwOptsReturnNonStructural;
+    assert(result5.get() == 84);
+
+    std::same_as<NonStructural> decltype(auto) result6 = *cwOptsReturnNonStructural;
+    assert(result6.get() == 0);
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}
diff --git a/libcxx/utils/generate_feature_test_macro_components.py b/libcxx/utils/generate_feature_test_macro_components.py
index 56a1d0c39c9fb..1ebd821c5b85e 100644
--- a/libcxx/utils/generate_feature_test_macro_components.py
+++ b/libcxx/utils/generate_feature_test_macro_components.py
@@ -334,6 +334,13 @@ def add_version_header(tc):
             "values": {"c++20": 202207},
             "headers": ["concepts"],
         },
+        {
+            "name": "__cpp_lib_constant_wrapper",
+            "values": {
+                "c++26": 202603,
+            },
+            "headers": ["utility"],
+        },
         {
             "name": "__cpp_lib_constexpr_algorithms",
             "values": {

>From a90086f3522131385ef71e793f6a2d83fe04178b Mon Sep 17 00:00:00 2001
From: Hui Xie <hui.xie1990 at gmail.com>
Date: Mon, 13 Apr 2026 14:19:28 +0100
Subject: [PATCH 2/3] ci

---
 libcxx/include/__utility/constant_wrapper.h | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/libcxx/include/__utility/constant_wrapper.h b/libcxx/include/__utility/constant_wrapper.h
index 440405fca304e..0b8009ea649bb 100644
--- a/libcxx/include/__utility/constant_wrapper.h
+++ b/libcxx/include/__utility/constant_wrapper.h
@@ -279,7 +279,7 @@ struct constant_wrapper : __cw_operators {
 private:
   template <class... _Args>
     requires(__constexpr_param<remove_cvref_t<_Args>> && ...)
-  _LIBCPP_HIDE_FROM_ABI static constexpr auto __call(_Args&&... __args) noexcept
+  _LIBCPP_HIDE_FROM_ABI static constexpr auto __call(_Args&&...) noexcept
       -> constant_wrapper<std::invoke(value, remove_cvref_t<_Args>::value...)> {
     return {};
   }
@@ -293,7 +293,7 @@ struct constant_wrapper : __cw_operators {
 
   template <class... _Args>
     requires(__constexpr_param<remove_cvref_t<_Args>> && ...)
-  _LIBCPP_HIDE_FROM_ABI static constexpr auto __subscript(_Args&&... __args) noexcept
+  _LIBCPP_HIDE_FROM_ABI static constexpr auto __subscript(_Args&&...) noexcept
       -> constant_wrapper<value[remove_cvref_t<_Args>::value...]> {
     return {};
   }

>From 3644ab97c788adc0b04154658b9a8b6f009f3a92 Mon Sep 17 00:00:00 2001
From: Hui Xie <hui.xie1990 at gmail.com>
Date: Mon, 13 Apr 2026 14:35:05 +0100
Subject: [PATCH 3/3] ci

---
 .../utilities/const.wrap.class/adl.pass.cpp   |  2 +-
 .../std/utilities/const.wrap.class/helpers.h  |  2 --
 .../const.wrap.class/pseudo_mutators.pass.cpp | 24 +++++++++++++++++++
 3 files changed, 25 insertions(+), 3 deletions(-)

diff --git a/libcxx/test/std/utilities/const.wrap.class/adl.pass.cpp b/libcxx/test/std/utilities/const.wrap.class/adl.pass.cpp
index 46653e2128497..fdd5905b5e364 100644
--- a/libcxx/test/std/utilities/const.wrap.class/adl.pass.cpp
+++ b/libcxx/test/std/utilities/const.wrap.class/adl.pass.cpp
@@ -13,7 +13,7 @@
 // [Note 1: The unnamed second template parameter to constant_wrapper is present
 // to aid argument-dependent lookup ([basic.lookup.argdep]) in finding overloads
 // for which constant_wrapper's wrapped value is a suitable argument, but for which
-// the constant_wrapper itself is not. — end note]
+// the constant_wrapper itself is not. - end note]
 
 #include <cassert>
 #include <concepts>
diff --git a/libcxx/test/std/utilities/const.wrap.class/helpers.h b/libcxx/test/std/utilities/const.wrap.class/helpers.h
index f94d604731331..a1f15bbc4bebd 100644
--- a/libcxx/test/std/utilities/const.wrap.class/helpers.h
+++ b/libcxx/test/std/utilities/const.wrap.class/helpers.h
@@ -9,7 +9,6 @@
 #ifndef TEST_STD_UTILITIES_CONST_WRAP_CLASS_HELPERS_H
 #define TEST_STD_UTILITIES_CONST_WRAP_CLASS_HELPERS_H
 
-
 struct NonStructural {
   constexpr NonStructural(int i) : value(i) {}
 
@@ -19,5 +18,4 @@ struct NonStructural {
   int value;
 };
 
-
 #endif // TEST_STD_UTILITIES_CONST_WRAP_CLASS_HELPERS_H
diff --git a/libcxx/test/std/utilities/const.wrap.class/pseudo_mutators.pass.cpp b/libcxx/test/std/utilities/const.wrap.class/pseudo_mutators.pass.cpp
index 100dca90d4601..e95cb91d01c8b 100644
--- a/libcxx/test/std/utilities/const.wrap.class/pseudo_mutators.pass.cpp
+++ b/libcxx/test/std/utilities/const.wrap.class/pseudo_mutators.pass.cpp
@@ -327,6 +327,28 @@ static_assert(!HasShiftLeftAssign<std::constant_wrapper<OptsReturnNonStructural{
 static_assert(!HasShiftRightAssign<std::constant_wrapper<OptsReturnNonStructural{6}>,
                                    std::constant_wrapper<OptsReturnNonStructural{1}>>);
 
+// LWG 4383. constant_wrapper's pseudo-mutators are underconstrained
+// https://cplusplus.github.io/LWG/issue4383
+constexpr void lwg4383_f(auto t) {
+  if constexpr (requires { +t; }) // ok
+    +t;
+  if constexpr (requires { -t; }) // ok
+    -t;
+  if constexpr (requires { ++t; }) // no hard error
+    ++t;
+  if constexpr (requires { --t; }) // no hard error
+    --t;
+}
+
+struct S {
+  /* constexpr */ int operator+() const { return 0; }
+  /* constexpr */ int operator++() { return 0; }
+  constexpr void operator-() const {}
+  constexpr void operator--() {}
+};
+
+constexpr void lwg4383() { lwg4383_f(std::cw<S{}>); }
+
 constexpr bool test() {
   {
     // WithOps increment/decrement
@@ -416,6 +438,8 @@ constexpr bool test() {
     static_assert(result10.value.value == 1);
   }
 
+  lwg4383();
+
   return true;
 }
 



More information about the libcxx-commits mailing list