[PATCH v2 2/2] libstdc++: Runtime fallback for constant_wrapper subscript and call operators.
Jonathan Wakely
jwakely@redhat.com
Tue Apr 14 11:06:38 GMT 2026
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
>
>
More information about the Libstdc++
mailing list