[v2,2/2] libstdc++: Runtime fallback for constant_wrapper subscript and call operators.
Commit Message
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
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
>
>
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
>
>
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
> >
> >
>
>
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
>> >
>> >
>>
@@ -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; }
@@ -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