On Tue, Sep 2, 2025 at 10:07 AM Luc Grosheintz <[email protected]> wrote:
> This is a partial implementation of P2781R9. It adds std::cw and > std::constant_wrapper, but doesn't modify __integral_constant_like for > span/mdspan. > > libstdc++-v3/ChangeLog: > > * include/bits/version.def (constant_wrapper): Add. > * include/bits/version.h: Regenerate. > * include/std/type_traits (_CwFixedValue): New class. > (_IndexSequence): New struct. > (_BuildIndexSequence): New struct. > (_ConstExprParam): New concept. > (_CwOperators): New struct. > (constant_wrapper): New struct. > (cw): New global constant. > * src/c++23/std.cc.in (constant_wrapper): Add. > (cw): Add. > * testsuite/20_util/constant_wrapper/adl.cc: New test. > * testsuite/20_util/constant_wrapper/ex.cc: New test. > * testsuite/20_util/constant_wrapper/generic.cc: New test. > * testsuite/20_util/constant_wrapper/instantiate.cc: New test. > * testsuite/20_util/constant_wrapper/op_comma_neg.cc: New test. > * testsuite/20_util/constant_wrapper/version.cc: New test. > > Signed-off-by: Luc Grosheintz <[email protected]> > Thanks, the implementation looks really solid. I have included decent amount of suggestion for the test, but this is mostly additional test cases. Generally, I would like to see additional test file, that would test with other constant-wrapper like, like integral_constant, and one defined in the test. Testing a few operators should be fine. For binary operators, we should also check that adding runtime value (like integer) produces runtime result, and not constant_wrapper. Similarly a small separate test should be fine. Finally, the assignment operators may work at runtime, if we have: struct ConstAssignable // tuple<int&> { ConstAssignable const& operator=(int) const { return *this; } }; cw<ConstAssignable> = 10; // would also compile and produce ConstAssignable. --- > libstdc++-v3/include/bits/version.def | 8 + > libstdc++-v3/include/bits/version.h | 10 + > libstdc++-v3/include/std/type_traits | 371 +++++++++++ > libstdc++-v3/src/c++23/std.cc.in | 4 + > .../testsuite/20_util/constant_wrapper/adl.cc | 42 ++ > .../testsuite/20_util/constant_wrapper/ex.cc | 45 ++ > .../20_util/constant_wrapper/generic.cc | 252 ++++++++ > .../20_util/constant_wrapper/instantiate.cc | 575 ++++++++++++++++++ > .../20_util/constant_wrapper/op_comma_neg.cc | 14 + > .../20_util/constant_wrapper/version.cc | 11 + > 10 files changed, 1332 insertions(+) > create mode 100644 libstdc++-v3/testsuite/20_util/constant_wrapper/adl.cc > create mode 100644 libstdc++-v3/testsuite/20_util/constant_wrapper/ex.cc > create mode 100644 > libstdc++-v3/testsuite/20_util/constant_wrapper/generic.cc > create mode 100644 > libstdc++-v3/testsuite/20_util/constant_wrapper/instantiate.cc > create mode 100644 > libstdc++-v3/testsuite/20_util/constant_wrapper/op_comma_neg.cc > create mode 100644 > libstdc++-v3/testsuite/20_util/constant_wrapper/version.cc > > diff --git a/libstdc++-v3/include/bits/version.def > b/libstdc++-v3/include/bits/version.def > index 84c755da10e..4f8d50bca30 100644 > --- a/libstdc++-v3/include/bits/version.def > +++ b/libstdc++-v3/include/bits/version.def > @@ -393,6 +393,14 @@ ftms = { > }; > }; > > +ftms = { > + name = constant_wrapper; > + values = { > + v = 202506; > + cxxmin = 26; > + }; > +}; > + > ftms = { > name = has_unique_object_representations; > values = { > diff --git a/libstdc++-v3/include/bits/version.h > b/libstdc++-v3/include/bits/version.h > index 410e3205339..2403584e57a 100644 > --- a/libstdc++-v3/include/bits/version.h > +++ b/libstdc++-v3/include/bits/version.h > @@ -430,6 +430,16 @@ > #endif /* !defined(__cpp_lib_byte) && defined(__glibcxx_want_byte) */ > #undef __glibcxx_want_byte > > +#if !defined(__cpp_lib_constant_wrapper) > +# if (__cplusplus > 202302L) > +# define __glibcxx_constant_wrapper 202506L > +# if defined(__glibcxx_want_all) || > defined(__glibcxx_want_constant_wrapper) > +# define __cpp_lib_constant_wrapper 202506L > +# endif > +# endif > +#endif /* !defined(__cpp_lib_constant_wrapper) && > defined(__glibcxx_want_constant_wrapper) */ > +#undef __glibcxx_want_constant_wrapper > + > #if !defined(__cpp_lib_has_unique_object_representations) > # if (__cplusplus >= 201703L) && > (defined(_GLIBCXX_HAVE_BUILTIN_HAS_UNIQ_OBJ_REP)) > # define __glibcxx_has_unique_object_representations 201606L > diff --git a/libstdc++-v3/include/std/type_traits > b/libstdc++-v3/include/std/type_traits > index 4636457eb5a..26cbbb4fd5b 100644 > --- a/libstdc++-v3/include/std/type_traits > +++ b/libstdc++-v3/include/std/type_traits > @@ -41,6 +41,7 @@ > > #define __glibcxx_want_bool_constant > #define __glibcxx_want_bounded_array_traits > +#define __glibcxx_want_constant_wrapper > #define __glibcxx_want_has_unique_object_representations > #define __glibcxx_want_integral_constant_callable > #define __glibcxx_want_is_aggregate > @@ -4302,6 +4303,376 @@ template<typename _Ret, typename _Fn, typename... > _Args> > }; > #endif // C++11 > > +#ifdef __cpp_lib_constant_wrapper // C++ >= 26 I wonder if we should put it into a separate file. This would be useful if we ever separate type traits, and it does not seem to have any dependencies. Like bits/constant_wrapper.h. It will be also used by submdspan later. + template<typename _Tp> > + struct _CwFixedValue > + { > + using _S_type = _Tp; > + > + constexpr > + _CwFixedValue(_S_type __v) noexcept > + : _M_data(__v) { } > + > + _S_type _M_data; > + }; > + > + template<typename _Tp, size_t _Extent> > + struct _CwFixedValue<_Tp[_Extent]> > + { > + using _S_type = _Tp[_Extent]; > + > + constexpr > + _CwFixedValue(_Tp (&__arr)[_Extent]) noexcept > + : _CwFixedValue(__arr, typename > _Build_index_tuple<_Extent>::__type()) > + { } > + > + template<size_t... _Indices> > + constexpr > + _CwFixedValue(_Tp (&__arr)[_Extent], _Index_tuple<_Indices...>) > noexcept > + : _M_data{__arr[_Indices]...} > + { } > + > + _Tp _M_data[_Extent]; > + }; > + > + template<typename _Tp, size_t _Extent> > + _CwFixedValue(_Tp (&)[_Extent]) -> _CwFixedValue<_Tp[_Extent]>; > + > + template<_CwFixedValue _Tp, > + typename = typename decltype(_CwFixedValue(_Tp))::_S_type> > + struct constant_wrapper; > + > + template<typename _Tp> > + concept _ConstExprParam = requires > + { > + typename constant_wrapper<_Tp::value>; > + }; > + > + struct _CwOperators > + { > + template<_ConstExprParam _Tp> > + friend constexpr auto > + operator+(_Tp) noexcept -> constant_wrapper<(+_Tp::value)> > + { return {}; } > + > + template<_ConstExprParam _Tp> > + friend constexpr auto > + operator-(_Tp) noexcept -> constant_wrapper<(-_Tp::value)> > + { return {}; } > + > + template<_ConstExprParam _Tp> > + friend constexpr auto > + operator~(_Tp) noexcept -> constant_wrapper<(~_Tp::value)> > + { return {}; } > + > + template<_ConstExprParam _Tp> > + friend constexpr auto > + operator!(_Tp) noexcept -> constant_wrapper<(!_Tp::value)> > + { return {}; } > + > + template<_ConstExprParam _Tp> > + friend constexpr auto > + operator&(_Tp) noexcept -> constant_wrapper<(&_Tp::value)> > + { return {}; } > + > + template<_ConstExprParam _Tp> > + friend constexpr auto > + operator*(_Tp) noexcept -> constant_wrapper<(*_Tp::value)> > + { return {}; } > + > + template<_ConstExprParam _Left, _ConstExprParam _Right> > + friend constexpr auto > + operator+(_Left, _Right) noexcept > + -> constant_wrapper<(_Left::value + _Right::value)> > + { return {}; } > + > + template<_ConstExprParam _Left, _ConstExprParam _Right> > + friend constexpr auto > + operator-(_Left, _Right) noexcept > + -> constant_wrapper<(_Left::value - _Right::value)> > + { return {}; } > + > + template<_ConstExprParam _Left, _ConstExprParam _Right> > + friend constexpr auto > + operator*(_Left, _Right) noexcept > + -> constant_wrapper<(_Left::value * _Right::value)> > + { return {}; } > + > + template<_ConstExprParam _Left, _ConstExprParam _Right> > + friend constexpr auto > + operator/(_Left, _Right) noexcept > + -> constant_wrapper<(_Left::value / _Right::value)> > + { return {}; } > + > + template<_ConstExprParam _Left, _ConstExprParam _Right> > + friend constexpr auto > + operator%(_Left, _Right) noexcept > + -> constant_wrapper<(_Left::value % _Right::value)> > + { return {}; } > + > + template<_ConstExprParam _Left, _ConstExprParam _Right> > + friend constexpr auto > + operator<<(_Left, _Right) noexcept > + -> constant_wrapper<(_Left::value << _Right::value)> > + { return {}; } > + > + template<_ConstExprParam _Left, _ConstExprParam _Right> > + friend constexpr auto > + operator>>(_Left, _Right) noexcept > + -> constant_wrapper<(_Left::value >> _Right::value)> > + { return {}; } > + > + template<_ConstExprParam _Left, _ConstExprParam _Right> > + friend constexpr auto > + operator&(_Left, _Right) noexcept > + -> constant_wrapper<(_Left::value & _Right::value)> > + { return {}; } > + > + template<_ConstExprParam _Left, _ConstExprParam _Right> > + friend constexpr auto > + operator|(_Left, _Right) noexcept > + -> constant_wrapper<(_Left::value | _Right::value)> > + { return {}; } > + > + template<_ConstExprParam _Left, _ConstExprParam _Right> > + friend constexpr auto > + operator^(_Left, _Right) noexcept > + -> constant_wrapper<(_Left::value ^ _Right::value)> > + { return {}; } > + > + template<_ConstExprParam _Left, _ConstExprParam _Right> > + requires (!is_constructible_v<bool, decltype(_Left::value)> > + || !is_constructible_v<bool, decltype(_Right::value)>) > + friend constexpr auto > + operator&&(_Left, _Right) noexcept > + -> constant_wrapper<(_Left::value && _Right::value)> > + { return {}; } > + > + template<_ConstExprParam _Left, _ConstExprParam _Right> > + requires (!is_constructible_v<bool, decltype(_Left::value)> > + || !is_constructible_v<bool, decltype(_Right::value)>) > + friend constexpr auto > + operator||(_Left, _Right) noexcept > + -> constant_wrapper<(_Left::value || _Right::value)> > + { return {}; } > + > + template<_ConstExprParam _Left, _ConstExprParam _Right> > + friend constexpr auto > + operator<=>(_Left, _Right) noexcept > + -> constant_wrapper<(_Left::value <=> _Right::value)> > + { return {}; } > + > + template<_ConstExprParam _Left, _ConstExprParam _Right> > + friend constexpr auto > + operator<(_Left, _Right) noexcept > + -> constant_wrapper<(_Left::value < _Right::value)> > + { return {}; } > + > + template<_ConstExprParam _Left, _ConstExprParam _Right> > + friend constexpr auto > + operator<=(_Left, _Right) noexcept > + -> constant_wrapper<(_Left::value <= _Right::value)> > + { return {}; } > + > + template<_ConstExprParam _Left, _ConstExprParam _Right> > + friend constexpr auto > + operator==(_Left, _Right) noexcept > + -> constant_wrapper<(_Left::value == _Right::value)> > + { return {}; } > + > + template<_ConstExprParam _Left, _ConstExprParam _Right> > + friend constexpr auto > + operator!=(_Left, _Right) noexcept > + -> constant_wrapper<(_Left::value != _Right::value)> > + { return {}; } > + > + template<_ConstExprParam _Left, _ConstExprParam _Right> > + friend constexpr auto > + operator>(_Left, _Right) noexcept > + -> constant_wrapper<(_Left::value > _Right::value)> > + { return {}; } > + > + template<_ConstExprParam _Left, _ConstExprParam _Right> > + friend constexpr auto > + operator>=(_Left, _Right) noexcept > + -> constant_wrapper<(_Left::value >= _Right::value)> > + { return {}; } > + > + template<_ConstExprParam _Left, _ConstExprParam _Right> > + friend constexpr auto > + operator,(_Left, _Right) noexcept = delete; > + > + template<_ConstExprParam _Left, _ConstExprParam _Right> > + friend constexpr auto > + operator->*(_Left, _Right) noexcept > + -> 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 > + requires requires(_Tp::value_type __x) { ++__x; } > + { > + return constant_wrapper< > + [] { auto __x = _Tp::value; return ++__x; }()>{}; > + } > + > + template<_ConstExprParam _Tp> > + constexpr auto > + operator++(this _Tp, int) noexcept > + requires requires(_Tp::value_type __x) { __x++; } > + { > + return constant_wrapper< > + [] { auto __x = _Tp::value; return __x++; }()>{}; > + } > + > + template<_ConstExprParam _Tp> > + constexpr auto > + operator--(this _Tp) noexcept > + requires requires(_Tp::value_type __x) { --__x; } > + { > + return constant_wrapper< > + [] { auto __x = _Tp::value; return --__x; }()>{}; > + } > + > + template<_ConstExprParam _Tp> > + constexpr auto > + operator--(this _Tp, int) noexcept > + requires requires(_Tp::value_type __x) { __x--; } > + { > + return constant_wrapper< > + [] { auto __x = _Tp::value; return __x--; }()>{}; > + } > + > + template<_ConstExprParam _Tp, _ConstExprParam _Right> > + constexpr auto > + operator+=(this _Tp, _Right) noexcept > + requires requires(_Tp::value_type __x) { __x += _Right::value; } > + { > + return constant_wrapper< > + [] { auto __x = _Tp::value; return __x += _Right::value; }()>{}; > + } > + > + template<_ConstExprParam _Tp, _ConstExprParam _Right> > + constexpr auto > + operator-=(this _Tp, _Right) noexcept > + requires requires(_Tp::value_type __x) { __x -= _Right::value; } > + { > + return constant_wrapper< > + [] { auto __x = _Tp::value; return __x -= _Right::value; }()>{}; > + } > + > + template<_ConstExprParam _Tp, _ConstExprParam _Right> > + constexpr auto > + operator*=(this _Tp, _Right) noexcept > + requires requires(_Tp::value_type __x) { __x *= _Right::value; } > + { > + return constant_wrapper< > + [] { auto __x = _Tp::value; return __x *= _Right::value; }()>{}; > + } > + > + template<_ConstExprParam _Tp, _ConstExprParam _Right> > + constexpr auto > + operator/=(this _Tp, _Right) noexcept > + requires requires(_Tp::value_type __x) { __x /= _Right::value; } > + { > + return constant_wrapper< > + [] { auto __x = _Tp::value; return __x /= _Right::value; }()>{}; > + } > + > + template<_ConstExprParam _Tp, _ConstExprParam _Right> > + constexpr auto > + operator%=(this _Tp, _Right) noexcept > + requires requires(_Tp::value_type __x) { __x %= _Right::value; } > + { > + return constant_wrapper< > + [] { auto __x = _Tp::value; return __x %= _Right::value; }()>{}; > + } > + > + template<_ConstExprParam _Tp, _ConstExprParam _Right> > + constexpr auto > + operator&=(this _Tp, _Right) noexcept > + requires requires(_Tp::value_type __x) { __x &= _Right::value; } > + { > + return constant_wrapper< > + [] { auto __x = _Tp::value; return __x &= _Right::value; }()>{}; > + } > + > + template<_ConstExprParam _Tp, _ConstExprParam _Right> > + constexpr auto > + operator|=(this _Tp, _Right) noexcept > + requires requires(_Tp::value_type __x) { __x |= _Right::value; } > + { > + return constant_wrapper< > + [] { auto __x = _Tp::value; return __x |= _Right::value; }()>{}; > + } > + > + template<_ConstExprParam _Tp, _ConstExprParam _Right> > + constexpr auto > + operator^=(this _Tp, _Right) noexcept > + requires requires(_Tp::value_type __x) { __x ^= _Right::value; } > + { > + return constant_wrapper< > + [] { auto __x = _Tp::value; return __x ^= _Right::value; }()>{}; > + } > + > + template<_ConstExprParam _Tp, _ConstExprParam _Right> > + constexpr auto > + operator<<=(this _Tp, _Right) noexcept > + requires requires(_Tp::value_type __x) { __x <<= _Right::value; } > + { > + return constant_wrapper< > + [] { auto __x = _Tp::value; return __x <<= _Right::value; }()>{}; > + } > + > + template<_ConstExprParam _Tp, _ConstExprParam _Right> > + constexpr auto > + operator>>=(this _Tp, _Right) noexcept > + requires requires(_Tp::value_type __x) { __x >>= _Right::value; } > + { > + return constant_wrapper< > + [] { auto __x = _Tp::value; return __x >>= _Right::value; }()>{}; > + } > + }; > + > + template<_CwFixedValue _X, typename> > + struct constant_wrapper : _CwOperators > + { > + static constexpr const auto& value = _X._M_data; > + using type = constant_wrapper; > + using value_type = typename decltype(_X)::_S_type; > + > + template<_ConstExprParam _Right> > + constexpr auto > + operator=(_Right) const noexcept > + requires requires(value_type __x) { __x = _Right::value; } > + { > + return constant_wrapper< > + [] { auto __x = value; return __x = _Right::value; }()>{}; > + } > + > + constexpr > + operator decltype(auto)() const noexcept > + { return value; } > + }; > + > + template<_CwFixedValue _Tp> > + constexpr auto cw = constant_wrapper<_Tp>{}; > +#endif > + > /// @} group metaprogramming > > _GLIBCXX_END_NAMESPACE_VERSION > diff --git a/libstdc++-v3/src/c++23/std.cc.in b/libstdc++-v3/src/c++23/ > std.cc.in > index 4888b8b4f23..a217a87330b 100644 > --- a/libstdc++-v3/src/c++23/std.cc.in > +++ b/libstdc++-v3/src/c++23/std.cc.in > @@ -2997,6 +2997,10 @@ export namespace std > using std::conditional_t; > using std::conjunction; > using std::conjunction_v; > +#if __cpp_lib_constant_wrapper > + using std::constant_wrapper; > + using std::cw; > +#endif > using std::decay; > using std::decay_t; > using std::disjunction; > diff --git a/libstdc++-v3/testsuite/20_util/constant_wrapper/adl.cc > b/libstdc++-v3/testsuite/20_util/constant_wrapper/adl.cc > new file mode 100644 > index 00000000000..7ee754fe4b1 > --- /dev/null > +++ b/libstdc++-v3/testsuite/20_util/constant_wrapper/adl.cc > @@ -0,0 +1,42 @@ > +// { dg-do run { target c++26 } } > +#include <type_traits> > + > +#include <testsuite_hooks.h> > + > +namespace adl > +{ > + struct Addable > + { > + double x; > + > + friend constexpr Addable > + operator+(Addable lhs, Addable rhs) > + { return Addable{lhs.x + rhs.x}; } > + > + friend constexpr bool > + operator==(Addable lhs, Addable rhs) > + { return lhs.x == rhs.x; } > + }; > +} > + > +constexpr void > +test_addable() > +{ > + auto check = [](auto a, auto b) > + { > + if constexpr (a + b == adl::Addable{5.0}) > + return true; > + else > + return false; > + }; > + > + constexpr adl::Addable a{2.0}, b{3.0}; > + VERIFY(check(std::cw<a>, std::cw<b>)); > +} > + > +int > +main() > +{ > + test_addable(); > + return 0; > +} > diff --git a/libstdc++-v3/testsuite/20_util/constant_wrapper/ex.cc > b/libstdc++-v3/testsuite/20_util/constant_wrapper/ex.cc > new file mode 100644 > index 00000000000..f46af929030 > --- /dev/null > +++ b/libstdc++-v3/testsuite/20_util/constant_wrapper/ex.cc > @@ -0,0 +1,45 @@ > +// { dg-do run { target c++26 } } > This could be changed to dg-do compile, as we do not really care about runtime value, just final_phase compiling. > +#include <type_traits> > + > +#include <testsuite_hooks.h> > + > +constexpr auto > +initial_phase(auto quantity_1, auto quantity_2) > +{ return quantity_1 + quantity_2; } > + > +constexpr auto > +middle_phase(auto tbd) > +{ return tbd; } > + > +constexpr bool > +final_phase(auto gathered, auto available) > +{ > + if constexpr (gathered == available) > + return true; > + else > + return false; > +} > + > +void > +impeccable_underground_planning() > +{ > + auto gathered_quantity = middle_phase(initial_phase(std::cw<42>, > std::cw<13>)); > + static_assert(gathered_quantity == 55); > + auto all_available = std::cw<55>; > + VERIFY(final_phase(gathered_quantity, all_available)); > +} > + > +// void > +// deeply_flawed_underground_planning() > Then we could mark that we expect error from here. > +// { > +// constexpr auto gathered_quantity = middle_phase(initial_phase(42, > 13)); > +// constexpr auto all_available = 55; > +// final_phase(gathered_quantity, all_available); > +// } > + > +int > +main() > +{ > + impeccable_underground_planning(); > + return 0; > +} > diff --git a/libstdc++-v3/testsuite/20_util/constant_wrapper/generic.cc > b/libstdc++-v3/testsuite/20_util/constant_wrapper/generic.cc > new file mode 100644 > index 00000000000..a74ce0b1d8b > --- /dev/null > +++ b/libstdc++-v3/testsuite/20_util/constant_wrapper/generic.cc > @@ -0,0 +1,252 @@ > +// { dg-do run { target c++26 } } > +#include <type_traits> > +#include <utility> > + > +#include <testsuite_hooks.h> > + > +constexpr void > +test_c_arrays() > Could you add a test for string-literals, their support was a major reason for having all the complications. > +{ > + constexpr double x[] = {1.1, 2.2, 3.3}; > + auto access = [](auto x, size_t i) > + { return x[i]; }; > + > + VERIFY(access(std::cw<x>, 0) == x[0]); > + VERIFY(access(std::cw<x>, 1) == x[1]); > + VERIFY(access(std::cw<x>, 2) == x[2]); > +} > + > +constexpr void > +test_ints() > +{ > + std::constant_wrapper<2> two; > + std::constant_wrapper<3> three; > + std::constant_wrapper<5> five; > + > + VERIFY(two + 3 == 5); > + static_assert(std::same_as<decltype(two + 3), int>); > + > + VERIFY(two + three == 5); > + VERIFY(two + three == five); > + static_assert(std::same_as<decltype(two + three), > std::constant_wrapper<5>>); > + > + VERIFY(two == std::cw<2>); > + VERIFY(two + 3 == std::cw<5>); > +} > + > +constexpr int > +add(int i, int j) > +{ return i + j; } > + > +struct Add > +{ > + constexpr int > + operator()(int i, int j) const noexcept > + { return i + j; } > +}; > + > +constexpr void > +test_function_object() > +{ > + auto cadd = std::cw<Add{}>; > + auto ci = std::cw<2>; > + auto cj = std::cw<3>; > + > + VERIFY(cadd(ci, cj) == 5); > + static_assert(std::same_as<decltype(cadd(ci, cj)), > std::constant_wrapper<5>>); > + > + // Invalid: > + // VERIFY(cadd(2, cj) == 5); > + // VERIFY(cadd(2, 3) == 5); > Please add negative tests for this, they can be done by static asserting that cadd is not invokable. > +} > + > +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>); > Similar here, test that operator[] does not work with runtime values. > +} > + > +struct Indexable1 > +{ > + constexpr int > + operator[](int i, int j) const noexcept > + { return i*j; } > +}; > + > +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>>); > +} > + > +struct Indexable2 > +{ > + template<typename I, typename J> > + constexpr int > + operator[](I i, J j) const noexcept > + { return i*j; } > +}; > + > +constexpr void > +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>>); > +} > + > +struct Indexable3 > +{ > + template<typename... Is> > + constexpr int > + operator[](Is... i) const noexcept > + { return (1 * ... * i); } > +}; > + > +constexpr void > +test_indexable3() > +{ > + auto cind = std::cw<Indexable3{}>; > + auto ci = std::cw<2>; > + auto cj = std::cw<3>; > + VERIFY(cind[] == 1); > + static_assert(std::same_as<decltype(cind[]), std::constant_wrapper<1>>); > + VERIFY(cind[ci] == ci); > + static_assert(std::same_as<decltype(cind[ci]), > std::constant_wrapper<2>>); > + VERIFY(cind[ci, cj] == ci*cj); > + static_assert(std::same_as<decltype(cind[ci, cj]), > std::constant_wrapper<6>>); > +} > + > +struct Divide > +{ > + int value; > + > + constexpr int > + divide(int div) const > + { return value / div; } > + > +}; > + > +constexpr void > +test_member_pointer() > +{ > + auto cdiv = std::cw<&Divide::divide>; > + auto co = std::cw<Divide{42}>; > + VERIFY(((&co)->* cdiv)(3) == co.value.value / 3); > I would prefer if we would also use static_assert to check that constant_wrapper is produced. I think the following should also work and produce int. (&co)->*(&Divide::divide) Similary: &co::value->cdiv > +} > + > +constexpr void > +test_pseudo_mutator() > +{ > + auto ci = std::cw<3>; > + auto cmmi = --ci; > + VERIFY(ci.value == 3); > + VERIFY(cmmi.value == 2); > + > + auto cimm = ci--; > + VERIFY(ci.value == 3); > + VERIFY(cimm.value == 3); > +} > + > +struct Truthy > +{ > + constexpr operator bool() const > + { return true; } > +}; > + > +constexpr void > +test_logic() > +{ > + auto ctrue = std::cw<true>; > + auto cfalse = std::cw<false>; > + auto truthy = Truthy{}; > + > + VERIFY(ctrue && ctrue); > Please also add checks for the type of expression, the above should be constant wrapper. The remaining to not. > + VERIFY(ctrue || cfalse); > + VERIFY(truthy && true); > + VERIFY((std::cw<0> < std::cw<1>) && (std::cw<1> < std::cw<5>)); > + > + // auto ctruthy = std::cw<Truthy{}>; > It would be good to put them in a negative test, as we test for specific constraint. I mean static_assert on concept should be fine. > + // Invalid: > + // VERIFY(ctrue && ctruthy); > + // VERIFY(true && ctruthy); > +} > + > +struct ThreeWayComp > +{ > + friend > + constexpr std::strong_ordering > + operator<=>(ThreeWayComp lhs, ThreeWayComp rhs) > + { return lhs.value <=> rhs.value; } > + > + int value; > +}; > + > +constexpr void > +test_three_way() > +{ > + VERIFY(std::cw<ThreeWayComp{0}> < std::cw<ThreeWayComp{1}>); > + VERIFY(std::cw<ThreeWayComp{2}> > std::cw<ThreeWayComp{1}>); > + VERIFY(std::cw<ThreeWayComp{2}> >= std::cw<ThreeWayComp{1}>); > + VERIFY(std::cw<ThreeWayComp{0}> <= std::cw<ThreeWayComp{1}>); > Test also with runtime values. > +} > + > +struct EqualityComp > +{ > + friend > + constexpr bool > + operator==(EqualityComp lhs, EqualityComp rhs) > + { return lhs.value == rhs.value; } > + > + int value; > +}; > + > +constexpr void > +test_equality() > +{ > + VERIFY(std::cw<EqualityComp{1}> == std::cw<EqualityComp{1}>); > + VERIFY(std::cw<EqualityComp{0}> != std::cw<EqualityComp{1}>); > Similar here, test comparing against runtime values. > +} > + > +constexpr bool > +test_all() > +{ > + test_c_arrays(); > + test_ints(); > + test_function_object(); > + test_function_pointer(); > + test_indexable1(); > + test_indexable2(); > + test_indexable3(); > + test_member_pointer(); > + test_pseudo_mutator(); > + test_logic(); > + test_three_way(); > + test_equality(); > + return true; > +} > + > +int > +main() > +{ > + test_all(); > + static_assert(test_all()); > + return 0; > +} > diff --git > a/libstdc++-v3/testsuite/20_util/constant_wrapper/instantiate.cc > b/libstdc++-v3/testsuite/20_util/constant_wrapper/instantiate.cc > new file mode 100644 > index 00000000000..4f1232598d6 > --- /dev/null > +++ b/libstdc++-v3/testsuite/20_util/constant_wrapper/instantiate.cc > @@ -0,0 +1,575 @@ > +// { dg-do run { target c++26 } } > +#include <type_traits> > +#include <utility> > + > +#include <testsuite_hooks.h> > + > +namespace free_ops > +{ > + template<int OpId> > + struct UnaryOps > + { > + friend constexpr int > + operator+(UnaryOps) noexcept requires (OpId == 0) > + { return OpId; } > + > + friend constexpr int > + operator-(UnaryOps) noexcept requires (OpId == 1) > + { return OpId; } > + > + friend constexpr int > + operator~(UnaryOps) noexcept requires (OpId == 2) > + { return OpId; } > + > + friend constexpr int > + operator!(UnaryOps) noexcept requires (OpId == 3) > + { return OpId; } > + > + friend constexpr int > + operator&(UnaryOps) noexcept requires (OpId == 4) > + { return OpId; } > + > + friend constexpr int > + operator*(UnaryOps) noexcept requires (OpId == 5) > + { return OpId; } > + > + friend constexpr int > + operator++(UnaryOps) noexcept requires (OpId == 6) > + { return OpId; } > + > + friend constexpr int > + operator++(UnaryOps, int) noexcept requires (OpId == 7) > + { return OpId; } > + > + friend constexpr int > + operator--(UnaryOps) noexcept requires (OpId == 8) > + { return OpId; } > + > + friend constexpr int > + operator--(UnaryOps, int) noexcept requires (OpId == 9) > + { return OpId; } > + }; > +} > + > +namespace member_ops > +{ > + template<int OpId> > + struct UnaryOps > + { > + constexpr int > + operator+() const noexcept requires (OpId == 0) > + { return OpId; } > + > + constexpr int > + operator-() const noexcept requires (OpId == 1) > + { return OpId; } > + > + constexpr int > + operator~() const noexcept requires (OpId == 2) > + { return OpId; } > + > + constexpr int > + operator!() const noexcept requires (OpId == 3) > + { return OpId; } > + > + constexpr int > + operator&() const noexcept requires (OpId == 4) > + { return OpId; } > + > + constexpr int > + operator*() const noexcept requires (OpId == 5) > + { return OpId; } > + > + constexpr int > + operator++() const noexcept requires (OpId == 6) > + { return OpId; } > + > + constexpr int > + operator++(int) const noexcept requires (OpId == 7) > + { return OpId; } > + > + constexpr int > + operator--() const noexcept requires (OpId == 8) > + { return OpId; } > + > + constexpr int > + operator--(int) const noexcept requires (OpId == 9) > + { return OpId; } > + }; > +} > + > +constexpr size_t n_unary_ops = 10; > + > +template<template<int> typename Ops, int OpId> > + constexpr void > + test_unary_operator() > + { > + auto x = std::cw<Ops<OpId>{}>; > + > + auto check = [](auto c) > + { > + VERIFY(c == OpId); > + static_assert(std::same_as<decltype(c), > std::constant_wrapper<OpId>>); > + }; > + > + if constexpr (OpId == 0) > + check(+x); > + if constexpr (OpId == 1) > + check(-x); > + if constexpr (OpId == 2) > + check(~x); > + if constexpr (OpId == 3) > + check(!x); > + if constexpr (OpId == 4) > + check(&x); > + if constexpr (OpId == 5) > + check(*x); > + if constexpr (OpId == 6) > + check(++x); > + if constexpr (OpId == 7) > + check(x++); > + if constexpr (OpId == 8) > + check(--x); > + if constexpr (OpId == 9) > + check(x--); > + > + static_assert(n_unary_ops == 10); > + } > + > +constexpr void > +test_unary_operators_all() > +{ > + auto run = []<size_t... Idx>(std::integer_sequence<size_t, Idx...>) > + { > + (test_unary_operator<free_ops::UnaryOps, Idx>(), ...); > + (test_unary_operator<member_ops::UnaryOps, Idx>(), ...); > + }; > + run(std::make_index_sequence<n_unary_ops>()); > +} > + > +namespace free_ops > +{ > + template<int OpId> > + struct BinaryOps > + { > + friend constexpr int > + operator+(BinaryOps, BinaryOps) noexcept requires (OpId == 0) > + { return OpId; } > + > + friend constexpr int > + operator-(BinaryOps, BinaryOps) noexcept requires (OpId == 1) > + { return OpId; } > + > + friend constexpr int > + operator*(BinaryOps, BinaryOps) noexcept requires (OpId == 2) > + { return OpId; } > + > + friend constexpr int > + operator/(BinaryOps, BinaryOps) noexcept requires (OpId == 3) > + { return OpId; } > + > + friend constexpr int > + operator%(BinaryOps, BinaryOps) noexcept requires (OpId == 4) > + { return OpId; } > + > + friend constexpr int > + operator<<(BinaryOps, BinaryOps) noexcept requires (OpId == 5) > + { return OpId; } > + > + friend constexpr int > + operator>>(BinaryOps, BinaryOps) noexcept requires (OpId == 6) > + { return OpId; } > + > + friend constexpr int > + operator&(BinaryOps, BinaryOps) noexcept requires (OpId == 7) > + { return OpId; } > + > + friend constexpr int > + operator|(BinaryOps, BinaryOps) noexcept requires (OpId == 8) > + { return OpId; } > + > + friend constexpr int > + operator^(BinaryOps, BinaryOps) noexcept requires (OpId == 9) > + { return OpId; } > + > + friend constexpr int > + operator&&(BinaryOps, BinaryOps) noexcept requires (OpId == 10) > + { return OpId; } > + > + friend constexpr int > + operator||(BinaryOps, BinaryOps) noexcept requires (OpId == 11) > + { return OpId; } > + > + friend constexpr int > + operator<=>(BinaryOps, BinaryOps) noexcept requires (OpId == 12) > + { return OpId; } > + > + friend constexpr int > + operator<(BinaryOps, BinaryOps) noexcept requires (OpId == 13) > + { return OpId; } > + > + friend constexpr int > + operator<=(BinaryOps, BinaryOps) noexcept requires (OpId == 14) > + { return OpId; } > + > + friend constexpr int > + operator==(BinaryOps, BinaryOps) noexcept requires (OpId == 15) > + { return OpId; } > + > + friend constexpr int > + operator!=(BinaryOps, BinaryOps) noexcept requires (OpId == 16) > + { return OpId; } > + > + friend constexpr int > + operator>(BinaryOps, BinaryOps) noexcept requires (OpId == 17) > + { return OpId; } > + > + friend constexpr int > + operator>=(BinaryOps, BinaryOps) noexcept requires (OpId == 18) > + { return OpId; } > + > + friend constexpr int > + operator+=(BinaryOps, BinaryOps) noexcept requires (OpId == 19) > + { return OpId; } > + > + friend constexpr int > + operator-=(BinaryOps, BinaryOps) noexcept requires (OpId == 20) > + { return OpId; } > + > + friend constexpr int > + operator*=(BinaryOps, BinaryOps) noexcept requires (OpId == 21) > + { return OpId; } > + > + friend constexpr int > + operator/=(BinaryOps, BinaryOps) noexcept requires (OpId == 22) > + { return OpId; } > + > + friend constexpr int > + operator%=(BinaryOps, BinaryOps) noexcept requires (OpId == 23) > + { return OpId; } > + > + friend constexpr int > + operator&=(BinaryOps, BinaryOps) noexcept requires (OpId == 24) > + { return OpId; } > + > + friend constexpr int > + operator|=(BinaryOps, BinaryOps) noexcept requires (OpId == 25) > + { return OpId; } > + friend constexpr int > + > + operator^=(BinaryOps, BinaryOps) noexcept requires (OpId == 26) > + { return OpId; } > + > + friend constexpr int > + operator<<=(BinaryOps, BinaryOps) noexcept requires (OpId == 27) > + { return OpId; } > + > + friend constexpr int > + operator>>=(BinaryOps, BinaryOps) noexcept requires (OpId == 28) > + { return OpId; } > + }; > +} > + > +namespace member_ops > +{ > + template<int OpId> > + struct BinaryOps > + { > + constexpr int > + operator+(BinaryOps) const noexcept requires (OpId == 0) > + { return OpId; } > + > + constexpr int > + operator-(BinaryOps) const noexcept requires (OpId == 1) > + { return OpId; } > + > + constexpr int > + operator*(BinaryOps) const noexcept requires (OpId == 2) > + { return OpId; } > + > + constexpr int > + operator/(BinaryOps) const noexcept requires (OpId == 3) > + { return OpId; } > + > + constexpr int > + operator%(BinaryOps) const noexcept requires (OpId == 4) > + { return OpId; } > + > + constexpr int > + operator<<(BinaryOps) const noexcept requires (OpId == 5) > + { return OpId; } > + > + constexpr int > + operator>>(BinaryOps) const noexcept requires (OpId == 6) > + { return OpId; } > + > + constexpr int > + operator&(BinaryOps) const noexcept requires (OpId == 7) > + { return OpId; } > + > + constexpr int > + operator|(BinaryOps) const noexcept requires (OpId == 8) > + { return OpId; } > + > + constexpr int > + operator^(BinaryOps) const noexcept requires (OpId == 9) > + { return OpId; } > + > + constexpr int > + operator&&(BinaryOps) const noexcept requires (OpId == 10) > + { return OpId; } > + > + constexpr int > + operator||(BinaryOps) const noexcept requires (OpId == 11) > + { return OpId; } > + > + constexpr int > + operator<=>(BinaryOps) const noexcept requires (OpId == 12) > + { return OpId; } > + > + constexpr int > + operator<(BinaryOps) const noexcept requires (OpId == 13) > + { return OpId; } > + > + constexpr int > + operator<=(BinaryOps) const noexcept requires (OpId == 14) > + { return OpId; } > + > + constexpr int > + operator==(BinaryOps) const noexcept requires (OpId == 15) > + { return OpId; } > + > + constexpr int > + operator!=(BinaryOps) const noexcept requires (OpId == 16) > + { return OpId; } > + > + constexpr int > + operator>(BinaryOps) const noexcept requires (OpId == 17) > + { return OpId; } > + > + constexpr int > + operator>=(BinaryOps) const noexcept requires (OpId == 18) > + { return OpId; } > + > + constexpr int > + operator+=(BinaryOps) const noexcept requires (OpId == 19) > + { return OpId; } > + > + constexpr int > + operator-=(BinaryOps) const noexcept requires (OpId == 20) > + { return OpId; } > + > + constexpr int > + operator*=(BinaryOps) const noexcept requires (OpId == 21) > + { return OpId; } > + > + constexpr int > + operator/=(BinaryOps) const noexcept requires (OpId == 22) > + { return OpId; } > + > + constexpr int > + operator%=(BinaryOps) const noexcept requires (OpId == 23) > + { return OpId; } > + > + constexpr int > + operator&=(BinaryOps) const noexcept requires (OpId == 24) > + { return OpId; } > + > + constexpr int > + operator|=(BinaryOps) const noexcept requires (OpId == 25) > + { return OpId; } > + > + constexpr int > + operator^=(BinaryOps) const noexcept requires (OpId == 26) > + { return OpId; } > + > + constexpr int > + operator<<=(BinaryOps) const noexcept requires (OpId == 27) > + { return OpId; } > + > + constexpr int > + operator>>=(BinaryOps) const noexcept requires (OpId == 28) > + { return OpId; } > + }; > +} > + > +constexpr size_t n_binary_ops = 29; > + > +template<template<int> typename Ops, int OpId> > + constexpr void > + test_binary_operator() > + { > + auto cx = std::cw<Ops<OpId>{}>; > + auto cy = std::cw<Ops<OpId>{}>; > + > + auto check = [](auto c) > + { > + VERIFY(c == OpId); > + static_assert(std::same_as<decltype(c), > std::constant_wrapper<OpId>>); > + }; > + > + if constexpr (OpId == 0) > + check(cx + cy); > + if constexpr (OpId == 1) > + check(cx - cy); > + if constexpr (OpId == 2) > + check(cx * cy); > + if constexpr (OpId == 3) > + check(cx / cy); > + if constexpr (OpId == 4) > + check(cx % cy); > + if constexpr (OpId == 5) > + check(cx << cy); > + if constexpr (OpId == 6) > + check(cx >> cy); > + if constexpr (OpId == 7) > + check(cx & cy); > + if constexpr (OpId == 8) > + check(cx | cy); > + if constexpr (OpId == 10) > + check(cx && cy); > + if constexpr (OpId == 11) > + check(cx || cy); > + if constexpr (OpId == 12) > + check(cx <=> cy); > + if constexpr (OpId == 13) > + check(cx < cy); > + if constexpr (OpId == 14) > + check(cx <= cy); > + if constexpr (OpId == 15) > + check(cx == cy); > + if constexpr (OpId == 16) > + check(cx != cy); > + if constexpr (OpId == 17) > + check(cx > cy); > + if constexpr (OpId == 18) > + check(cx >= cy); > + if constexpr (OpId == 19) > + check(cx += cy); > + if constexpr (OpId == 20) > + check(cx -= cy); > + if constexpr (OpId == 21) > + check(cx *= cy); > + if constexpr (OpId == 22) > + check(cx /= cy); > + if constexpr (OpId == 23) > + check(cx %= cy); > + if constexpr (OpId == 24) > + check(cx &= cy); > + if constexpr (OpId == 25) > + check(cx |= cy); > + if constexpr (OpId == 26) > + check(cx ^= cy); > + if constexpr (OpId == 27) > + check(cx <<= cy); > + if constexpr (OpId == 28) > + check(cx >>= cy); > + static_assert(n_binary_ops == 29); > + } > + > +template<template<int> typename Ops, int OpId> > + constexpr void > + test_mixed_binary_operators() > + { > + constexpr auto x = Ops<OpId>{}; > + auto cx = std::cw<x>; > + constexpr auto y = Ops<OpId>{}; > + auto cy = std::cw<y>; > + > + auto check = [](auto vc, auto cv) > + { > + auto impl = [](auto c) > + { > + VERIFY(c == OpId); > + static_assert(std::same_as<decltype(c), int>); > + }; > + > + impl(vc); > + impl(cv); > + }; > + > + if constexpr (OpId == 0) > + check(x + cy, cx + y); > + if constexpr (OpId == 1) > + check(x - cy, cx - y); > + if constexpr (OpId == 2) > + check(x * cy, cx * y); > + if constexpr (OpId == 3) > + check(x / cy, cx / y); > + if constexpr (OpId == 4) > + check(x % cy, cx % y); > + if constexpr (OpId == 5) > + check(x << cy, cx << y); > + if constexpr (OpId == 6) > + check(x >> cy, cx >> y); > + if constexpr (OpId == 7) > + check(x & cy, cx & y); > + if constexpr (OpId == 8) > + check(x | cy, cx | y); > + if constexpr (OpId == 10) > + check(x && cy, cx && y); > + if constexpr (OpId == 11) > + check(x || cy, cx || y); > + if constexpr (OpId == 12) > + check(x <=> cy, cx <=> y); > + if constexpr (OpId == 13) > + check(x < cy, cx < y); > + if constexpr (OpId == 14) > + check(x <= cy, cx <= y); > + if constexpr (OpId == 15) > + check(x == cy, cx == y); > + if constexpr (OpId == 16) > + check(x != cy, cx != y); > + if constexpr (OpId == 17) > + check(x > cy, cx > y); > + if constexpr (OpId == 18) > + check(x >= cy, cx >= y); > + if constexpr (OpId == 19) > + check(x += cy, cx += y); > + if constexpr (OpId == 20) > + check(x -= cy, cx -= y); > + if constexpr (OpId == 21) > + check(x *= cy, cx *= y); > + if constexpr (OpId == 22) > + check(x /= cy, cx /= y); > + if constexpr (OpId == 23) > + check(x %= cy, cx %= y); > + if constexpr (OpId == 24) > + check(x &= cy, cx &= y); > + if constexpr (OpId == 25) > + check(x |= cy, cx |= y); > + if constexpr (OpId == 26) > + check(x ^= cy, cx ^= y); > + if constexpr (OpId == 27) > + check(x <<= cy, cx <<= y); > + if constexpr (OpId == 28) > + check(x >>= cy, cx >>= y); > + static_assert(n_binary_ops == 29); > + } > + > +constexpr void > +test_binary_operators_all() > +{ > + auto run = []<size_t... Idx>(std::integer_sequence<size_t, Idx...>) > + { > + (test_binary_operator<free_ops::BinaryOps, Idx>(), ...); > + (test_mixed_binary_operators<free_ops::BinaryOps, Idx>(), ...); > + (test_binary_operator<member_ops::BinaryOps, Idx>(), ...); > + }; > + run(std::make_index_sequence<n_binary_ops>()); > +} > + > +constexpr bool > +test_all() > +{ > + test_unary_operators_all(); > + test_binary_operators_all(); > + return true; > +} > + > +int > +main() > +{ > + test_all(); > + return 0; > +} > diff --git > a/libstdc++-v3/testsuite/20_util/constant_wrapper/op_comma_neg.cc > b/libstdc++-v3/testsuite/20_util/constant_wrapper/op_comma_neg.cc > new file mode 100644 > index 00000000000..4384e920ea5 > --- /dev/null > +++ b/libstdc++-v3/testsuite/20_util/constant_wrapper/op_comma_neg.cc > @@ -0,0 +1,14 @@ > +// { dg-do compile { target c++26 } } > +#include <type_traits> > + > +constexpr void > +test_comma_same_types() > +{ > + (std::cw<1>, std::cw<2>); // { dg-error "use of deleted function" } > +} > + > +constexpr void > +test_comma_different_types() > +{ > + (std::cw<1>, std::cw<2.0>); // { dg-error "use of deleted function" } > +} > diff --git a/libstdc++-v3/testsuite/20_util/constant_wrapper/version.cc > b/libstdc++-v3/testsuite/20_util/constant_wrapper/version.cc > new file mode 100644 > index 00000000000..4fee6159141 > --- /dev/null > +++ b/libstdc++-v3/testsuite/20_util/constant_wrapper/version.cc > @@ -0,0 +1,11 @@ > +// { dg-do preprocess { target c++26 } } > +// { dg-add-options no_pch } > + > +#include <type_traits> > + > +#ifndef __cpp_lib_constant_wrapper > +#error "Feature test macro __cpp_lib_constant_wrapper is missing for > <type_traits>" > +#if __cpp_lib_constant_wrapper < 202506L > +#error "Feature test macro __cpp_lib_constant_wrapper has the wrong value" > +#endif > +#endif > -- > 2.51.0 > >
