[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.
---
There is a Mathias patch, that I started out with, but I ended up doing
a complete rewrite, including the test cases.
Testing on x86_64-linux. *constant_wrapper* test passed.
OK for trunk when all test passes?
libstdc++-v3/include/bits/utility.h | 52 +++-
.../20_util/constant_wrapper/generic.cc | 261 ++++++++++++++----
2 files changed, 242 insertions(+), 71 deletions(-)
Comments
Fixed issues below locally.
On Tue, Apr 7, 2026 at 6:56 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.
> ---
> There is a Mathias patch, that I started out with, but I ended up doing
> a complete rewrite, including the test cases.
>
> Testing on x86_64-linux. *constant_wrapper* test passed.
> OK for trunk when all test passes?
>
> libstdc++-v3/include/bits/utility.h | 52 +++-
> .../20_util/constant_wrapper/generic.cc | 261 ++++++++++++++----
> 2 files changed, 242 insertions(+), 71 deletions(-)
>
> diff --git a/libstdc++-v3/include/bits/utility.h
> b/libstdc++-v3/include/bits/utility.h
> index 970e63e8170..ff4667bea5b 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, static_cast<_Args&&>(__args)...);
std::forward instead of static_cast.
> + }
> +
> + 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[__args...];
>
Forward is missing here. I will add a test case.
> + }
> +
> 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..03cb2c3fa96 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,216 @@ constexpr int
> add(int i, int j)
> { return i + j; }
>
> -struct Add
> +template<bool Noexcept>
> +struct CAdd
> {
> constexpr int
> - operator()(int i, int j) const noexcept
> + operator()(int i, int j) const noexcept(Noexcept)
> { return i + j; }
> };
>
> -constexpr void
> -test_function_object()
> +template<bool Noexcept>
> +struct RAdd
> +{
> + int
> + operator()(int i, int j) const noexcept(Noexcept)
> + { return i + j; }
> +};
> +
> +struct CMixedAdd
> +{
> + static constexpr int
> + operator()(int i, int j)
> + { return i + j; }
> +
> + template <auto N1, auto N2>
> + static constexpr int operator()
> + (std::constant_wrapper<N1, int> i, std::constant_wrapper<N2, int> j)
> + { return 100 + i + j; }
> +};
> +
> +struct RMixedAdd
> +{
> + static int
> + operator()(int i, int j)
> + { return i + j; }
> +
> + template <auto N1, auto N2>
> + static int operator()
> + (std::constant_wrapper<N1, int> i, std::constant_wrapper<N2, int> j)
> + { return 100 + i + j; }
> +};
> +
> +template<typename T1, typename T2>
> +struct AtLeastOneInt
> {
> - auto check = [](auto cfo)
> + 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; }
> +};
> +
> +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);
>
> - 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 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);
> + }
> +
> +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; }>();
> +
> + 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{}>();
> + // Cannot return value wrapped in constant_wrapper because operator
> + // is not constexpr, fullbacks 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), 105);
> + check_same(std::cw<RMixedAdd{}>(2, 3), 105);
> }
>
> constexpr void
> test_function_pointer()
> {
> - auto cptr = std::cw<add>;
> - auto ci = std::cw<2>;
> - auto cj = std::cw<3>;
> -
> - VERIFY(cptr(ci, cj) == 5);
> - static_assert(std::same_as<decltype(cptr(ci, cj)),
> std::constant_wrapper<5>>);
> -
> - VERIFY(cptr(2, cj) == 5);
> - static_assert(std::same_as<decltype(cptr(2, cj)), int>);
> -
> - VERIFY(cptr(2, 3) == 5);
> - static_assert(std::same_as<decltype(cptr(2, 3)), int>);
> + check_invoke<true, add>();
> }
>
> -struct Indexable1
> +template<bool Noexcept>
> +struct CIndex
> +{
> + constexpr int
> + operator[](int i, int j) const noexcept(Noexcept)
> + { return i*j; }
> +};
> +
> +template<bool Noexcept>
> +struct RIndex
> {
> constexpr int
> - operator[](int i, int j) const noexcept
> + operator[](int i, int j) const noexcept(Noexcept)
> { return i*j; }
> };
>
> -template<typename Obj, typename... Args>
> - concept indexable = requires (Obj obj, Args... args)
> +struct CMixedIndex
> +{
> + static constexpr int
> + operator[](int i, int j)
> + { return i * j; }
> +
> + template <auto N1, auto N2>
> + static constexpr int operator[]
> + (std::constant_wrapper<N1, int> i, std::constant_wrapper<N2, int> j)
> + { return 100 + i * j; }
> +};
> +
> +struct RMixedIndex
> +{
> + static int
> + operator[](int i, int j)
> + { return i * j; }
> +
> + template <auto N1, auto N2>
> + static int operator[]
> + (std::constant_wrapper<N1, int> i, std::constant_wrapper<N2, int> j)
> + { return 100 + i * j; }
> +};
> +
> +struct PoisonedIndex
> +{
> + template<typename T1, typename T2>
> + constexpr static
> + typename AtLeastOneInt<T1, T2>::type
> + operator[](T1 i, T2 j) noexcept
> + { return i * j; }
> +};
> +
> +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>{}>();
> + check_subscript<true, RIndex<true>{}>();
> + check_subscript<true, 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{}>();
> + // Cannot return value wrapped in constant_wrapper because operator
> + // is not constexpr, fullbacks to runtime call, that selects
>
"fullback" -> "fallback"
> + // (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], 106);
> + check_same(std::cw<RMixedIndex{}>[2, 3], 106);
> }
>
> struct Indexable2
> @@ -182,12 +323,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 +372,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
> @@ -355,11 +504,7 @@ test_all()
> {
> test_c_arrays();
> test_ints();
> - test_function_object();
> test_function_pointer();
> - test_indexable1();
> - test_indexable2();
> - test_indexable3();
> test_member_pointer();
> test_logic();
> test_three_way();
> --
> 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, static_cast<_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[__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,216 @@ constexpr int
add(int i, int j)
{ return i + j; }
-struct Add
+template<bool Noexcept>
+struct CAdd
{
constexpr int
- operator()(int i, int j) const noexcept
+ operator()(int i, int j) const noexcept(Noexcept)
{ return i + j; }
};
-constexpr void
-test_function_object()
+template<bool Noexcept>
+struct RAdd
+{
+ int
+ operator()(int i, int j) const noexcept(Noexcept)
+ { return i + j; }
+};
+
+struct CMixedAdd
+{
+ static constexpr int
+ operator()(int i, int j)
+ { return i + j; }
+
+ template <auto N1, auto N2>
+ static constexpr int operator()
+ (std::constant_wrapper<N1, int> i, std::constant_wrapper<N2, int> j)
+ { return 100 + i + j; }
+};
+
+struct RMixedAdd
+{
+ static int
+ operator()(int i, int j)
+ { return i + j; }
+
+ template <auto N1, auto N2>
+ static int operator()
+ (std::constant_wrapper<N1, int> i, std::constant_wrapper<N2, int> j)
+ { return 100 + i + j; }
+};
+
+template<typename T1, typename T2>
+struct AtLeastOneInt
{
- auto check = [](auto cfo)
+ 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; }
+};
+
+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);
- 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 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);
+ }
+
+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; }>();
+
+ 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{}>();
+ // Cannot return value wrapped in constant_wrapper because operator
+ // is not constexpr, fullbacks 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), 105);
+ check_same(std::cw<RMixedAdd{}>(2, 3), 105);
}
constexpr void
test_function_pointer()
{
- auto cptr = std::cw<add>;
- auto ci = std::cw<2>;
- auto cj = std::cw<3>;
-
- VERIFY(cptr(ci, cj) == 5);
- static_assert(std::same_as<decltype(cptr(ci, cj)), std::constant_wrapper<5>>);
-
- VERIFY(cptr(2, cj) == 5);
- static_assert(std::same_as<decltype(cptr(2, cj)), int>);
-
- VERIFY(cptr(2, 3) == 5);
- static_assert(std::same_as<decltype(cptr(2, 3)), int>);
+ check_invoke<true, add>();
}
-struct Indexable1
+template<bool Noexcept>
+struct CIndex
+{
+ constexpr int
+ operator[](int i, int j) const noexcept(Noexcept)
+ { return i*j; }
+};
+
+template<bool Noexcept>
+struct RIndex
{
constexpr int
- operator[](int i, int j) const noexcept
+ operator[](int i, int j) const noexcept(Noexcept)
{ return i*j; }
};
-template<typename Obj, typename... Args>
- concept indexable = requires (Obj obj, Args... args)
+struct CMixedIndex
+{
+ static constexpr int
+ operator[](int i, int j)
+ { return i * j; }
+
+ template <auto N1, auto N2>
+ static constexpr int operator[]
+ (std::constant_wrapper<N1, int> i, std::constant_wrapper<N2, int> j)
+ { return 100 + i * j; }
+};
+
+struct RMixedIndex
+{
+ static int
+ operator[](int i, int j)
+ { return i * j; }
+
+ template <auto N1, auto N2>
+ static int operator[]
+ (std::constant_wrapper<N1, int> i, std::constant_wrapper<N2, int> j)
+ { return 100 + i * j; }
+};
+
+struct PoisonedIndex
+{
+ template<typename T1, typename T2>
+ constexpr static
+ typename AtLeastOneInt<T1, T2>::type
+ operator[](T1 i, T2 j) noexcept
+ { return i * j; }
+};
+
+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>{}>();
+ check_subscript<true, RIndex<true>{}>();
+ check_subscript<true, 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{}>();
+ // Cannot return value wrapped in constant_wrapper because operator
+ // is not constexpr, fullbacks 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], 106);
+ check_same(std::cw<RMixedIndex{}>[2, 3], 106);
}
struct Indexable2
@@ -182,12 +323,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 +372,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
@@ -355,11 +504,7 @@ test_all()
{
test_c_arrays();
test_ints();
- test_function_object();
test_function_pointer();
- test_indexable1();
- test_indexable2();
- test_indexable3();
test_member_pointer();
test_logic();
test_three_way();