[v2,2/2] libstdc++: Runtime fallback for constant_wrapper subscript and call operators.

Message ID 20260407185705.643937-1-tkaminsk@redhat.com
State New
Headers
Series None |

Commit Message

Tomasz Kaminski April 7, 2026, 6:55 p.m. UTC
  This implements P3978R3: constant_wrapper should unwrap on call and subscript.

The operator() and operator[] are now fallbacking 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 cosntructed (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/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.
---
v2:
 * forwards arguments in runtime path
 * added test for MoveOnly argument types
 * enable test_function_object and other test that will accidentally
   disabled
 * add if !consteval for non-constexpr (R prefixed types)

Tested *constant_wrapper* again. Testing full on x86_64-linux.
OK for trunk when all test passes.

 libstdc++-v3/include/bits/utility.h           |  52 +++-
 .../20_util/constant_wrapper/generic.cc       | 293 ++++++++++++++----
 2 files changed, 279 insertions(+), 66 deletions(-)
  

Comments

Tomasz Kaminski April 8, 2026, 12:58 p.m. UTC | #1
On Tue, Apr 7, 2026 at 8:58 PM Tomasz Kamiński <tkaminsk@redhat.com> wrote:

> This implements P3978R3: constant_wrapper should unwrap on call and
> subscript.
>
> The operator() and operator[] are now fallbacking 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 cosntructed (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/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.
> ---
> v2:
>  * forwards arguments in runtime path
>  * added test for MoveOnly argument types
>  * enable test_function_object and other test that will accidentally
>    disabled
>  * add if !consteval for non-constexpr (R prefixed types)
>
> Tested *constant_wrapper* again. Testing full on x86_64-linux.
> OK for trunk when all test passes.
>
Bumped constant_wrapper FTM locally:
--- a/libstdc++-v3/include/bits/version.def
+++ b/libstdc++-v3/include/bits/version.def
@@ -403,6 +403,11 @@ ftms = {

 ftms = {
   name = constant_wrapper;
+  // P3978R3 constant_wrapper should unwrap on call and subscript
+  values = {
+    v = 202603;
+    cxxmin = 26;
+  };
   values = {
     v = 202506;


>
>  libstdc++-v3/include/bits/utility.h           |  52 +++-
>  .../20_util/constant_wrapper/generic.cc       | 293 ++++++++++++++----
>  2 files changed, 279 insertions(+), 66 deletions(-)
>
> diff --git a/libstdc++-v3/include/bits/utility.h
> b/libstdc++-v3/include/bits/utility.h
> index 970e63e8170..75576c0655c 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/testsuite/20_util/constant_wrapper/generic.cc
> b/libstdc++-v3/testsuite/20_util/constant_wrapper/generic.cc
> index 1c7770d7a89..1773185ab2b 100644
> --- a/libstdc++-v3/testsuite/20_util/constant_wrapper/generic.cc
> +++ b/libstdc++-v3/testsuite/20_util/constant_wrapper/generic.cc
> @@ -1,4 +1,5 @@
>  // { dg-do run { target c++26 } }
> +#include <functional>
>  #include <utility>
>  #include <string_view>
>
> @@ -20,9 +21,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 +97,254 @@ 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
>  {
> -  auto check = [](auto cfo)
> +  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
> +{
> +  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; }
> +};
> +
> +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});
>  }
>
>  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 Indexable1
> +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 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)
> +template<bool Constexpr, auto index>
> +  constexpr void
> +  check_subscript()
>    {
> -    obj[args...];
> -  };
> +    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}];
>  }
>
>  struct Indexable2
> @@ -182,12 +361,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 +410,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
> --
> 2.53.0
>
>
  
Jonathan Wakely April 14, 2026, 11:06 a.m. UTC | #2
On Tue, 07 Apr 2026 at 20:55 +0200, Tomasz Kamiński wrote:
>This implements P3978R3: constant_wrapper should unwrap on call and subscript.
>
>The operator() and operator[] are now fallbacking 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 cosntructed (constant_wrapper<value(....)>{}) is never throwing

But the __invoke(value, args...) expression used for the template
argument could throw, couldn't it?

Or the value[args...] expression.

>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/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.
>---
>v2:
> * forwards arguments in runtime path
> * added test for MoveOnly argument types
> * enable test_function_object and other test that will accidentally
>   disabled
> * add if !consteval for non-constexpr (R prefixed types)
>
>Tested *constant_wrapper* again. Testing full on x86_64-linux.
>OK for trunk when all test passes.
>
> libstdc++-v3/include/bits/utility.h           |  52 +++-
> .../20_util/constant_wrapper/generic.cc       | 293 ++++++++++++++----
> 2 files changed, 279 insertions(+), 66 deletions(-)
>
>diff --git a/libstdc++-v3/include/bits/utility.h b/libstdc++-v3/include/bits/utility.h
>index 970e63e8170..75576c0655c 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/testsuite/20_util/constant_wrapper/generic.cc b/libstdc++-v3/testsuite/20_util/constant_wrapper/generic.cc
>index 1c7770d7a89..1773185ab2b 100644
>--- a/libstdc++-v3/testsuite/20_util/constant_wrapper/generic.cc
>+++ b/libstdc++-v3/testsuite/20_util/constant_wrapper/generic.cc
>@@ -1,4 +1,5 @@
> // { dg-do run { target c++26 } }
>+#include <functional>
> #include <utility>
> #include <string_view>
>
>@@ -20,9 +21,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 +97,254 @@ 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
> {
>-  auto check = [](auto cfo)
>+  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
>+{
>+  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; }
>+};
>+
>+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});
> }
>
> 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 Indexable1
>+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 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)
>+template<bool Constexpr, auto index>
>+  constexpr void
>+  check_subscript()
>   {
>-    obj[args...];
>-  };
>+    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}];
> }
>
> struct Indexable2
>@@ -182,12 +361,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 +410,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
>-- 
>2.53.0
>
>
  
Tomasz Kaminski April 14, 2026, 11:13 a.m. UTC | #3
On Tue, Apr 14, 2026 at 1:06 PM Jonathan Wakely <jwakely@redhat.com> wrote:

> On Tue, 07 Apr 2026 at 20:55 +0200, Tomasz Kamiński wrote:
> >This implements P3978R3: constant_wrapper should unwrap on call and
> subscript.
> >
> >The operator() and operator[] are now fallbacking 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 cosntructed (constant_wrapper<value(....)>{}) is never throwing
>
> But the __invoke(value, args...) expression used for the template
> argument could throw, couldn't it?
>
> Or the value[args...] expression.
>
Yes, but evaluating a non-type template parameter starts a separate
evaluator,
and if it exits via exception, the type constant_wrapper<value(....)> is
ill-formed,
and we never go this path. It never get thrown from operator() evaluation.

See: https://godbolt.org/z/5cMPTbd7W


>
> >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/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.
> >---
> >v2:
> > * forwards arguments in runtime path
> > * added test for MoveOnly argument types
> > * enable test_function_object and other test that will accidentally
> >   disabled
> > * add if !consteval for non-constexpr (R prefixed types)
> >
> >Tested *constant_wrapper* again. Testing full on x86_64-linux.
> >OK for trunk when all test passes.
> >
> > libstdc++-v3/include/bits/utility.h           |  52 +++-
> > .../20_util/constant_wrapper/generic.cc       | 293 ++++++++++++++----
> > 2 files changed, 279 insertions(+), 66 deletions(-)
> >
> >diff --git a/libstdc++-v3/include/bits/utility.h
> b/libstdc++-v3/include/bits/utility.h
> >index 970e63e8170..75576c0655c 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/testsuite/20_util/constant_wrapper/generic.cc
> b/libstdc++-v3/testsuite/20_util/constant_wrapper/generic.cc
> >index 1c7770d7a89..1773185ab2b 100644
> >--- a/libstdc++-v3/testsuite/20_util/constant_wrapper/generic.cc
> >+++ b/libstdc++-v3/testsuite/20_util/constant_wrapper/generic.cc
> >@@ -1,4 +1,5 @@
> > // { dg-do run { target c++26 } }
> >+#include <functional>
> > #include <utility>
> > #include <string_view>
> >
> >@@ -20,9 +21,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 +97,254 @@ 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
> > {
> >-  auto check = [](auto cfo)
> >+  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
> >+{
> >+  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; }
> >+};
> >+
> >+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});
> > }
> >
> > 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 Indexable1
> >+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 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)
> >+template<bool Constexpr, auto index>
> >+  constexpr void
> >+  check_subscript()
> >   {
> >-    obj[args...];
> >-  };
> >+    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}];
> > }
> >
> > struct Indexable2
> >@@ -182,12 +361,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 +410,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
> >--
> >2.53.0
> >
> >
>
>
  
Jonathan Wakely April 14, 2026, 11:32 a.m. UTC | #4
On Tue, 14 Apr 2026 at 12:13, Tomasz Kaminski <tkaminsk@redhat.com> wrote:
>
>
>
> On Tue, Apr 14, 2026 at 1:06 PM Jonathan Wakely <jwakely@redhat.com> wrote:
>>
>> On Tue, 07 Apr 2026 at 20:55 +0200, Tomasz Kamiński wrote:
>> >This implements P3978R3: constant_wrapper should unwrap on call and subscript.
>> >
>> >The operator() and operator[] are now fallbacking 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 cosntructed (constant_wrapper<value(....)>{}) is never throwing
>>
>> But the __invoke(value, args...) expression used for the template
>> argument could throw, couldn't it?
>>
>> Or the value[args...] expression.
>
> Yes, but evaluating a non-type template parameter starts a separate evaluator,
> and if it exits via exception, the type constant_wrapper<value(....)> is ill-formed,
> and we never go this path. It never get thrown from operator() evaluation.

Ah, thanks. The requires-expression already checked that the type is
valid, so if the  _ConstExprInvocable bool is true, we know that
determining the type didn't throw.

Good work on the detailed tests.

OK for trunk then, thanks.

>
> See: https://godbolt.org/z/5cMPTbd7W
>
>>
>>
>> >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/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.
>> >---
>> >v2:
>> > * forwards arguments in runtime path
>> > * added test for MoveOnly argument types
>> > * enable test_function_object and other test that will accidentally
>> >   disabled
>> > * add if !consteval for non-constexpr (R prefixed types)
>> >
>> >Tested *constant_wrapper* again. Testing full on x86_64-linux.
>> >OK for trunk when all test passes.
>> >
>> > libstdc++-v3/include/bits/utility.h           |  52 +++-
>> > .../20_util/constant_wrapper/generic.cc       | 293 ++++++++++++++----
>> > 2 files changed, 279 insertions(+), 66 deletions(-)
>> >
>> >diff --git a/libstdc++-v3/include/bits/utility.h b/libstdc++-v3/include/bits/utility.h
>> >index 970e63e8170..75576c0655c 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/testsuite/20_util/constant_wrapper/generic.cc b/libstdc++-v3/testsuite/20_util/constant_wrapper/generic.cc
>> >index 1c7770d7a89..1773185ab2b 100644
>> >--- a/libstdc++-v3/testsuite/20_util/constant_wrapper/generic.cc
>> >+++ b/libstdc++-v3/testsuite/20_util/constant_wrapper/generic.cc
>> >@@ -1,4 +1,5 @@
>> > // { dg-do run { target c++26 } }
>> >+#include <functional>
>> > #include <utility>
>> > #include <string_view>
>> >
>> >@@ -20,9 +21,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 +97,254 @@ 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
>> > {
>> >-  auto check = [](auto cfo)
>> >+  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
>> >+{
>> >+  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; }
>> >+};
>> >+
>> >+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});
>> > }
>> >
>> > 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 Indexable1
>> >+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 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)
>> >+template<bool Constexpr, auto index>
>> >+  constexpr void
>> >+  check_subscript()
>> >   {
>> >-    obj[args...];
>> >-  };
>> >+    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}];
>> > }
>> >
>> > struct Indexable2
>> >@@ -182,12 +361,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 +410,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
>> >--
>> >2.53.0
>> >
>> >
>>
  

Patch

diff --git a/libstdc++-v3/include/bits/utility.h b/libstdc++-v3/include/bits/utility.h
index 970e63e8170..75576c0655c 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/testsuite/20_util/constant_wrapper/generic.cc b/libstdc++-v3/testsuite/20_util/constant_wrapper/generic.cc
index 1c7770d7a89..1773185ab2b 100644
--- a/libstdc++-v3/testsuite/20_util/constant_wrapper/generic.cc
+++ b/libstdc++-v3/testsuite/20_util/constant_wrapper/generic.cc
@@ -1,4 +1,5 @@ 
 // { dg-do run { target c++26 } }
+#include <functional>
 #include <utility>
 #include <string_view>
 
@@ -20,9 +21,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 +97,254 @@  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
 {
-  auto check = [](auto cfo)
+  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
+{
+  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; }
+};
+
+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});
 }
 
 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 Indexable1
+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 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)
+template<bool Constexpr, auto index>
+  constexpr void
+  check_subscript()
   {
-    obj[args...];
-  };
+    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}];
 }
 
 struct Indexable2
@@ -182,12 +361,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 +410,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