[committed v3] libstdc++: Runtime fallback for constant_wrapper subscript and call operators.

Tomasz Kamiński tkaminsk@redhat.com
Tue Apr 14 12:21:58 GMT 2026


This implements P3978R3: constant_wrapper should unwrap on call and subscript.

The operator() and operator[] are now fallback to calling corresponding
operation on value, if either arguments are not constant_wrapper like, or
the result of the invocation is not usable as non-type template argument
(non-constant). The call operator is also now defined in terms of invoke,
to support member pointers.

The noexcept specification is simplified, by observing that creating a
default constructed (constant_wrapper<value(....)>{}) is never throwing
operation. Nested requires expr is used for short-circuting, and thus
avoid checking viability of the operation on the value with
constant_wrapper only (see PoisonedAdd, PoisonedIndex in tests).

libstdc++-v3/ChangeLog:

	* include/bits/version.def (constant_wrapped): Updated to 202603L.
	* include/bits/version.h: Regenerate.
	* include/bits/utility.h (_CwOperators::operator())
	(_CwOperators:operator[]): Delete, they are now provided by...
	(constant_wrapper::operator(), constant_wrapper::operator[]):
	Define.
	* testsuite/20_util/constant_wrapper/generic.cc: Add additional
	test cases for invoke and subscript.
	* testsuite/20_util/constant_wrapper/version.cc: Update tested
	value.

Reviewed-by: Jonathan Wakely <jwakely@redhat.com>
Signed-off-by: Tomasz Kamiński <tkaminsk@redhat.com>
---
v3 includes test for situation when expression inside template argument
(call or subscript) exits via exception (and thus non-constexpr).

Tested on x86_64-linux locally. Pushed to trunk.

 libstdc++-v3/include/bits/utility.h           |  52 ++-
 libstdc++-v3/include/bits/version.def         |   4 +-
 libstdc++-v3/include/bits/version.h           |   4 +-
 .../20_util/constant_wrapper/generic.cc       | 342 +++++++++++++++---
 .../20_util/constant_wrapper/version.cc       |   2 +-
 5 files changed, 334 insertions(+), 70 deletions(-)

diff --git a/libstdc++-v3/include/bits/utility.h b/libstdc++-v3/include/bits/utility.h
index 74ae04d6bd2..dac02e4a479 100644
--- a/libstdc++-v3/include/bits/utility.h
+++ b/libstdc++-v3/include/bits/utility.h
@@ -41,6 +41,9 @@
 
 #include <type_traits>
 #include <bits/move.h>
+#ifdef __glibcxx_constant_wrapper // C++ >= 26
+#  include <bits/invoke.h>
+#endif
 
 namespace std _GLIBCXX_VISIBILITY(default)
 {
@@ -342,19 +345,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	-> constant_wrapper<_Left::value->*(_Right::value)>
       { return {}; }
 
-    template<_ConstExprParam _Tp, _ConstExprParam... _Args>
-      constexpr auto
-      operator()(this _Tp, _Args...) noexcept
-      requires
-	requires(_Args...) { constant_wrapper<_Tp::value(_Args::value...)>(); }
-      { return constant_wrapper<_Tp::value(_Args::value...)>{}; }
-
-    template<_ConstExprParam _Tp, _ConstExprParam... _Args>
-      constexpr auto
-      operator[](this _Tp, _Args...) noexcept
-	-> constant_wrapper<(_Tp::value[_Args::value...])>
-      { return {}; }
-
     template<_ConstExprParam _Tp>
       constexpr auto
       operator++(this _Tp) noexcept
@@ -453,6 +443,42 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
   	-> constant_wrapper<(value = _Right::value)>
       { return {}; }
 
+    template<typename... _Args,
+	     bool _ConstExprInvocable = requires {
+	       requires (_ConstExprParam<remove_cvref_t<_Args>> && ...);
+	       typename constant_wrapper<std::__invoke(value, remove_cvref_t<_Args>::value...)>;
+	     }>
+      requires _ConstExprInvocable || is_invocable_v<const value_type&, _Args...>
+      static constexpr decltype(auto)
+      operator()(_Args&&... __args)
+      noexcept(requires {
+	requires _ConstExprInvocable || is_nothrow_invocable_v<const value_type&, _Args...>;
+      })
+      {
+	if constexpr (_ConstExprInvocable)
+	  return constant_wrapper<std::__invoke(value, remove_cvref_t<_Args>::value...)>{};
+	else
+	  return std::__invoke(value, std::forward<_Args>(__args)...);
+      }
+
+    template<typename... _Args,
+	     bool _ConstExprSubscriptable = requires {
+	       requires (_ConstExprParam<remove_cvref_t<_Args>> && ...);
+	       typename constant_wrapper<value[remove_cvref_t<_Args>::value...]>;
+	     }>
+      requires _ConstExprSubscriptable || requires { value[std::declval<_Args>()...]; }
+      static constexpr decltype(auto)
+      operator[](_Args&&... __args)
+      noexcept(requires {
+	requires _ConstExprSubscriptable || noexcept(value[std::declval<_Args>()...]);
+      })
+      {
+	if constexpr (_ConstExprSubscriptable)
+	  return constant_wrapper<value[remove_cvref_t<_Args>::value...]>{};
+	else
+	  return value[std::forward<_Args>(__args)...];
+      }
+
     constexpr
     operator decltype(value)() const noexcept
     { return value; }
diff --git a/libstdc++-v3/include/bits/version.def b/libstdc++-v3/include/bits/version.def
index 7aaf150d087..94fc1f85993 100644
--- a/libstdc++-v3/include/bits/version.def
+++ b/libstdc++-v3/include/bits/version.def
@@ -408,8 +408,10 @@ ftms = {
 
 ftms = {
   name = constant_wrapper;
+  // 202506 P2781R9 std::constexpr_wrapper 
+  // 202603 P3978R3 constant_wrapper should unwrap on call and subscript
   values = {
-    v = 202506;
+    v = 202603;
     cxxmin = 26;
   };
 };
diff --git a/libstdc++-v3/include/bits/version.h b/libstdc++-v3/include/bits/version.h
index b247662f089..402e67580eb 100644
--- a/libstdc++-v3/include/bits/version.h
+++ b/libstdc++-v3/include/bits/version.h
@@ -447,9 +447,9 @@
 
 #if !defined(__cpp_lib_constant_wrapper)
 # if (__cplusplus >  202302L)
-#  define __glibcxx_constant_wrapper 202506L
+#  define __glibcxx_constant_wrapper 202603L
 #  if defined(__glibcxx_want_all) || defined(__glibcxx_want_constant_wrapper)
-#   define __cpp_lib_constant_wrapper 202506L
+#   define __cpp_lib_constant_wrapper 202603L
 #  endif
 # endif
 #endif /* !defined(__cpp_lib_constant_wrapper) */
diff --git a/libstdc++-v3/testsuite/20_util/constant_wrapper/generic.cc b/libstdc++-v3/testsuite/20_util/constant_wrapper/generic.cc
index 1c7770d7a89..7538efead49 100644
--- a/libstdc++-v3/testsuite/20_util/constant_wrapper/generic.cc
+++ b/libstdc++-v3/testsuite/20_util/constant_wrapper/generic.cc
@@ -1,6 +1,8 @@
 // { dg-do run { target c++26 } }
+#include <functional>
 #include <utility>
 #include <string_view>
+#include <stdexcept>
 
 #include <testsuite_hooks.h>
 
@@ -20,9 +22,9 @@ test_c_arrays()
   auto access = [](auto x, size_t i)
   { return x[i]; };
 
-  check_same(access(std::cw<x>, 0), x[0]);
-  check_same(access(std::cw<x>, 1), x[1]);
-  check_same(access(std::cw<x>, 2), x[2]);
+  check_same(std::cw<x>[0], x[0]);
+  check_same(std::cw<x>[1], x[1]);
+  check_same(std::cw<x>[2], x[2]);
 
   check_same(cx[std::cw<0>], std::cw<x[0]>);
   check_same(cx[std::cw<1>], std::cw<x[1]>);
@@ -96,76 +98,302 @@ constexpr int
 add(int i, int j)
 { return i + j; }
 
-struct Add
+template<bool Noexcept>
+struct CAdd
 {
   constexpr int
-  operator()(int i, int j) const noexcept
+  operator()(int i, int j) const noexcept(Noexcept)
   { return i + j; }
 };
 
-constexpr void
-test_function_object()
+template<bool Noexcept>
+struct RAdd
+{
+  int
+  operator()(int i, int j) const noexcept(Noexcept)
+  { return i + j; }
+};
+
+struct CMixedAdd
+{
+  static constexpr int
+  operator()(int i, int j)
+  { return i + j; }
+
+  template <auto N1, auto N2>
+    static constexpr int operator()
+    (std::constant_wrapper<N1, int> i, std::constant_wrapper<N2, int> j)
+    { return 100 + i + j; }
+};
+
+struct RMixedAdd
+{
+  static int
+  operator()(int i, int j)
+  { return i + j; }
+
+  template <auto N1, auto N2>
+    static int operator()
+    (std::constant_wrapper<N1, int> i, std::constant_wrapper<N2, int> j)
+    { return 100 + i + j; }
+};
+
+template<typename T1, typename T2>
+struct AtLeastOneInt
+{
+  static_assert(std::is_same_v<T1, int> || std::is_same_v<T2, int>);
+  using type = int;
+};
+
+struct PoisonedAdd
 {
-  auto check = [](auto cfo)
+  template<typename T1, typename T2>
+  constexpr static
+  typename AtLeastOneInt<T1, T2>::type 
+  operator()(T1 i, T2 j) noexcept
+  { return i + j; }
+};
+
+struct MoveOnly
+{
+  constexpr explicit
+  MoveOnly(int p) : v(p)
+  { }
+
+  MoveOnly(MoveOnly&&) = default;
+
+  int v;
+};
+
+struct MoveArgFunc
+{
+  constexpr int
+  operator()(MoveOnly arg) const
+  { return arg.v; }
+};
+
+struct ThrowFunc
+{
+  static constexpr int
+  operator()(int i, int j)
+  {
+    if (i < 0 || j < 0)
+      throw std::invalid_argument("negative");
+    return i + j;
+  }
+};
+
+template<bool Constexpr, auto functor>
+  constexpr void
+  check_invoke()
   {
     auto ci = std::cw<2>;
     auto cj = std::cw<3>;
+    auto cfo = std::cw<functor>;
 
-    VERIFY(cfo(ci, cj) == 5);
-    static_assert(std::same_as<decltype(cfo(ci, cj)), std::constant_wrapper<5>>);
+    if constexpr (Constexpr)
+      check_same(cfo(ci, cj), std::cw<5>);
+    else
+      check_same(cfo(ci, cj), 5);
 
-    static_assert(std::invocable<decltype(cfo), decltype(ci), decltype(cj)>);
-    static_assert(!std::invocable<decltype(cfo), int, decltype(cj)>);
-    static_assert(!std::invocable<decltype(cfo), int, int>);
-  };
+    check_same(cfo(2, cj), 5);
+    check_same(cfo(2, 3), 5);
+
+    constexpr bool Noexcept = noexcept(functor(2, 3));
+    static_assert(noexcept(cfo(ci, cj)) == Constexpr || Noexcept);
+    static_assert(noexcept(cfo(2, cj)) == Noexcept);
+    static_assert(noexcept(cfo(2, 3)) == Noexcept);
+  }
 
-  check(std::cw<Add{}>);
-  check(std::cw<[](int i, int j){ return i + j; }>);
-  check(std::cw<[](auto i, auto j){ return i + j; }>);
+constexpr void
+test_function_object()
+{
+  check_invoke<true, CAdd<true>{}>();
+  check_invoke<true, CAdd<false>{}>();
+  check_invoke<true, [](int i, int j) { return i + j; }>();
+  check_invoke<true, [](auto i, auto j) noexcept { return i + j; }>();
+  if !consteval {
+    check_invoke<false, RAdd<true>{}>();
+    check_invoke<false, RAdd<false>{}>();
+  }
+
+  // Check if constant_wrappers are not passed to value,
+  // if they can be unwrapped.
+  check_invoke<true, PoisonedAdd{}>();
+
+  // Prefer unwrapping constant_wrappers, so calls (int, int)
+  check_same(CMixedAdd{}(2, 3), 5);
+  check_same(CMixedAdd{}(std::cw<2>, std::cw<3>), 105);
+  check_same(std::cw<CMixedAdd{}>(std::cw<2>, std::cw<3>), std::cw<5>);
+  check_invoke<true, CMixedAdd{}>();
+  if !consteval {
+    // Cannot return value wrapped in constant_wrapper because operator
+    // is not constexpr, fallbacks to runtime call, that selects
+    // (constant_wrapper, constant_wrapper) overload
+    check_same(RMixedAdd{}(2, 3), 5);
+    check_same(RMixedAdd{}(std::cw<2>, std::cw<3>), 105);
+    check_same(std::cw<RMixedAdd{}>(std::cw<2>, std::cw<3>), 105);
+    check_same(std::cw<RMixedAdd{}>(std::cw<2>, 3), 5);
+    check_same(std::cw<RMixedAdd{}>(2, 3), 5);
+  }
+
+  // Test if arguments are fowarded
+  std::cw<MoveArgFunc{}>(MoveOnly{10});
+
+  // For positive arguments call do not throw, constant_wrapper type is valid.
+  check_invoke<true, ThrowFunc{}>();
+  // For negative arguments, the call exits via exception, and constant_wrapper 
+  // type is invalid, so we fallback to runtime.
+  static_assert(std::is_same_v<int, decltype(std::cw<ThrowFunc{}>(std::cw<-1>, std::cw<1>))>);
+  static_assert(!noexcept(std::cw<ThrowFunc{}>(std::cw<-1>, std::cw<1>)));
+  try {
+    std::cw<ThrowFunc{}>(std::cw<-1>, std::cw<1>);
+    VERIFY(false);
+  } catch (const std::invalid_argument&) {
+    VERIFY(true);
+  }
 }
 
 constexpr void
 test_function_pointer()
 {
-  auto cptr = std::cw<add>;
-  auto ci = std::cw<2>;
-  auto cj = std::cw<3>;
+  check_invoke<true, add>();
+}
 
-  VERIFY(cptr(ci, cj) == 5);
-  static_assert(std::same_as<decltype(cptr(ci, cj)), std::constant_wrapper<5>>);
+template<bool Noexcept>
+struct CIndex
+{
+  constexpr int
+  operator[](int i, int j) const noexcept(Noexcept)
+  { return i*j; }
+};
 
-  VERIFY(cptr(2, cj) == 5);
-  static_assert(std::same_as<decltype(cptr(2, cj)), int>);
+template<bool Noexcept>
+struct RIndex
+{
+  int
+  operator[](int i, int j) const noexcept(Noexcept)
+  { return i*j; }
+};
 
-  VERIFY(cptr(2, 3) == 5);
-  static_assert(std::same_as<decltype(cptr(2, 3)), int>);
-}
+struct CMixedIndex
+{
+  static constexpr int
+  operator[](int i, int j)
+  { return i * j; }
+
+  template <auto N1, auto N2>
+    static constexpr int operator[]
+    (std::constant_wrapper<N1, int> i, std::constant_wrapper<N2, int> j)
+    { return 100 + i * j; }
+};
+
+struct RMixedIndex
+{
+  static int
+  operator[](int i, int j)
+  { return i * j; }
+
+  template <auto N1, auto N2>
+    static int operator[]
+    (std::constant_wrapper<N1, int> i, std::constant_wrapper<N2, int> j)
+    { return 100 + i * j; }
+};
 
-struct Indexable1
+struct PoisonedIndex
+{
+  template<typename T1, typename T2>
+  constexpr static
+  typename AtLeastOneInt<T1, T2>::type 
+  operator[](T1 i, T2 j) noexcept
+  { return i * j; }
+};
+
+struct MoveArgIndex
 {
   constexpr int
-  operator[](int i, int j) const noexcept
-  { return i*j; }
+  operator[](MoveOnly arg) const
+  { return arg.v; }
 };
 
-template<typename Obj, typename... Args>
-  concept indexable = requires (Obj obj, Args... args)
+struct ThrowIndex
+{
+  static constexpr int
+  operator[](int i, int j)
   {
-    obj[args...];
-  };
+    if (i < 0 || j < 0)
+      throw std::invalid_argument("negative");
+    return i * j;
+  }
+};
+
+template<bool Constexpr, auto index>
+  constexpr void
+  check_subscript()
+  {
+    auto ci = std::cw<2>;
+    auto cj = std::cw<3>;
+    auto cio = std::cw<index>;
+
+    if constexpr (Constexpr)
+      check_same(cio[ci, cj], std::cw<6>);
+    else
+      check_same(cio[ci, cj], 6);
+
+    check_same(cio[2, cj], 6);
+    check_same(cio[2, 3], 6);
+
+    constexpr bool Noexcept = noexcept(index[2, 3]);
+    static_assert(noexcept(cio[ci, cj]) == Constexpr || Noexcept);
+    static_assert(noexcept(cio[2, cj]) == Noexcept);
+    static_assert(noexcept(cio[2, 3]) == Noexcept);
+  }
 
 constexpr void
 test_indexable1()
 {
-  auto cind = std::cw<Indexable1{}>;
-  auto ci = std::cw<2>;
-  auto cj = std::cw<3>;
-  VERIFY(cind[ci, cj] == ci*cj);
-  static_assert(std::same_as<decltype(cind[ci, cj]), std::constant_wrapper<6>>);
-
-  static_assert(indexable<decltype(cind), decltype(ci), decltype(cj)>);
-  static_assert(!indexable<decltype(cind), int, decltype(cj)>);
-  static_assert(!indexable<decltype(cind), int, int>);
+  check_subscript<true, CIndex<true>{}>();
+  check_subscript<true, CIndex<false>{}>();
+  if !consteval {
+    check_subscript<false, RIndex<true>{}>();
+    check_subscript<false, RIndex<false>{}>();
+  }
+
+  // Check if constant_wrappers are not passed to value,
+  // if they can be unwrapped.
+  check_subscript<true, PoisonedIndex{}>();
+
+  // Prefer unwrapping constant_wrappers, so calls (int, int)
+  check_same(CMixedIndex{}[2, 3], 6);
+  check_same(CMixedIndex{}[std::cw<2>, std::cw<3>], 106);
+  check_same(std::cw<CMixedIndex{}>[std::cw<2>, std::cw<3>], std::cw<6>);
+  check_subscript<true, CMixedIndex{}>();
+  if !consteval {
+    // Cannot return value wrapped in constant_wrapper because operator
+    // is not constexpr, fallbacks to runtime call, that selects
+    // (constant_wrapper, constant_wrapper) overload
+    check_same(RMixedIndex{}[2, 3], 6);
+    check_same(RMixedIndex{}[std::cw<2>, std::cw<3>], 106);
+    check_same(std::cw<RMixedIndex{}>[std::cw<2>, std::cw<3>], 106);
+    check_same(std::cw<RMixedIndex{}>[std::cw<2>, 3], 6);
+    check_same(std::cw<RMixedIndex{}>[2, 3], 6);
+  }
+
+  // Test if arguments are fowarded
+  std::cw<MoveArgIndex{}>[MoveOnly{10}];
+
+  // For positive arguments subscript do not throw, constant_wrapper type is valid.
+  check_subscript<true, ThrowIndex{}>();
+  // For negative arguments, the subscript exits via exception, and constant_wrapper 
+  // type is invalid, so we fallback to runtime.
+  static_assert(std::is_same_v<int, decltype(std::cw<ThrowIndex{}>[std::cw<-1>, std::cw<1>])>);
+  static_assert(!noexcept(std::cw<ThrowIndex{}>[std::cw<-1>, std::cw<1>]));
+  try {
+    std::cw<ThrowIndex{}>[std::cw<-1>, std::cw<1>];
+    VERIFY(false);
+  } catch (const std::invalid_argument&) {
+    VERIFY(true);
+  }
 }
 
 struct Indexable2
@@ -182,12 +410,9 @@ test_indexable2()
   auto cind = std::cw<Indexable2{}>;
   auto ci = std::cw<2>;
   auto cj = std::cw<3>;
-  VERIFY(cind[ci, cj] == ci*cj);
-  static_assert(std::same_as<decltype(cind[ci, cj]), std::constant_wrapper<6>>);
-
-  static_assert(indexable<decltype(cind), decltype(ci), decltype(cj)>);
-  static_assert(!indexable<decltype(cind), int, decltype(cj)>);
-  static_assert(!indexable<decltype(cind), int, int>);
+  check_same(cind[ci, cj], std::cw<6>);
+  check_same(cind[ci, 3], 6);
+  check_same(cind[2, 3], 6);
 }
 
 struct Indexable3
@@ -234,10 +459,21 @@ test_member_pointer()
   check_same((&co)->*(&Divide::value), nom);
   check_same(&(co.value)->*cvalue, nom);
 
-  auto expect_unwrapped = nom / denom;
-  check_same(((&co)->*(&Divide::divide))(denom), expect_unwrapped);
-  check_same((&(co.value)->*cdiv)(denom), expect_unwrapped);
-  check_same(((&decltype(co)::value)->*cdiv)(denom), expect_unwrapped);
+  check_same(cvalue(co), std::cw<nom>);
+  check_same(cvalue(co.value), nom);
+  check_same(cvalue(&co.value), nom);
+  check_same(cvalue(std::ref(co.value)), nom);
+
+  auto cresult = std::cw<nom / denom>;
+  check_same(((&co)->*(&Divide::divide))(denom), cresult.value);
+  check_same((&(co.value)->*cdiv)(denom), cresult.value);
+  check_same(((&decltype(co)::value)->*cdiv)(denom), cresult.value);
+
+  check_same(cdiv(co, std::cw<denom>), cresult); 
+  check_same(cdiv(co.value, std::cw<denom>), cresult.value); 
+  check_same(cdiv(co.value, denom), cresult.value); 
+  check_same(cdiv(&co.value, denom), cresult.value); 
+  check_same(cdiv(std::ref(co.value), denom), cresult.value); 
 }
 
 struct Truthy
diff --git a/libstdc++-v3/testsuite/20_util/constant_wrapper/version.cc b/libstdc++-v3/testsuite/20_util/constant_wrapper/version.cc
index 776d80c576a..7a620994e1f 100644
--- a/libstdc++-v3/testsuite/20_util/constant_wrapper/version.cc
+++ b/libstdc++-v3/testsuite/20_util/constant_wrapper/version.cc
@@ -5,7 +5,7 @@
 
 #ifndef __cpp_lib_constant_wrapper
 #error "Feature test macro __cpp_lib_constant_wrapper is missing for <utility>"
-#if __cpp_lib_constant_wrapper < 202506L
+#if __cpp_lib_constant_wrapper < 202603L
 #error "Feature test macro __cpp_lib_constant_wrapper has the wrong value"
 #endif
 #endif
-- 
2.53.0



More information about the Libstdc++ mailing list