From: Giuseppe D'Angelo Date: Mon, 9 Jun 2025 21:13:21 +0000 (+0200) Subject: libstdc++: add range support to std::optional (P3168) X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=774ae8734f0e199a8c6d29dd8c186b893385470b;p=thirdparty%2Fgcc.git libstdc++: add range support to std::optional (P3168) This commit implements P3168 ("Give std::optional Range Support"), added for C++26. Both begin() and end() are straightforward, implemented using normal_iterator over a raw pointer. std::optional is also a view, so specialize enable_view for it. We also need to disable automatic formatting a std::optional as a range by specializing format_kind. In order to avoid dragging when including , I've isolated format_kind and some supporting code into so that I can use that (comparatively) lighter header. libstdc++-v3/ChangeLog: * include/bits/formatfwd.h (format_kind): Move the definition (and some supporting code) from . * include/std/format (format_kind): Likewise. * include/bits/version.def (optional_range_support): Add the feature-testing macro. * include/bits/version.h: Regenerate. * include/std/optional (iterator, const_iterator, begin, end): Add range support. (enable_view): Specialize for std::optional. (format_kind): Specialize for std::optional. * testsuite/20_util/optional/range.cc: New test. * testsuite/20_util/optional/version.cc: Test the new feature-testing macro. --- diff --git a/libstdc++-v3/include/bits/formatfwd.h b/libstdc++-v3/include/bits/formatfwd.h index 777e6290f744..314b55d50bcd 100644 --- a/libstdc++-v3/include/bits/formatfwd.h +++ b/libstdc++-v3/include/bits/formatfwd.h @@ -162,6 +162,32 @@ namespace __format using __maybe_const = __conditional_t, const _Tp, _Tp>; } + + // [format.range], formatting of ranges + // [format.range.fmtkind], variable template format_kind + enum class range_format { + disabled, + map, + set, + sequence, + string, + debug_string + }; + + /** @brief A constant determining how a range should be formatted. + * + * The primary template of `std::format_kind` cannot be instantiated. + * There is a partial specialization for input ranges and you can + * specialize the variable template for your own cv-unqualified types + * that satisfy the `ranges::input_range` concept. + * + * @since C++23 + */ + template + constexpr auto format_kind = []{ + static_assert(false, "cannot use primary template of 'std::format_kind'"); + return type_identity<_Rg>{}; + }(); #endif // format_ranges _GLIBCXX_END_NAMESPACE_VERSION diff --git a/libstdc++-v3/include/bits/version.def b/libstdc++-v3/include/bits/version.def index 7cf62e989aa7..880586e91260 100644 --- a/libstdc++-v3/include/bits/version.def +++ b/libstdc++-v3/include/bits/version.def @@ -851,6 +851,14 @@ ftms = { }; }; +ftms = { + name = optional_range_support; + values = { + v = 202406; + cxxmin = 26; + }; +}; + ftms = { name = destroying_delete; values = { diff --git a/libstdc++-v3/include/bits/version.h b/libstdc++-v3/include/bits/version.h index 9f4cf9a3425e..4300adb22762 100644 --- a/libstdc++-v3/include/bits/version.h +++ b/libstdc++-v3/include/bits/version.h @@ -955,6 +955,16 @@ #endif /* !defined(__cpp_lib_optional) && defined(__glibcxx_want_optional) */ #undef __glibcxx_want_optional +#if !defined(__cpp_lib_optional_range_support) +# if (__cplusplus > 202302L) +# define __glibcxx_optional_range_support 202406L +# if defined(__glibcxx_want_all) || defined(__glibcxx_want_optional_range_support) +# define __cpp_lib_optional_range_support 202406L +# endif +# endif +#endif /* !defined(__cpp_lib_optional_range_support) && defined(__glibcxx_want_optional_range_support) */ +#undef __glibcxx_want_optional_range_support + #if !defined(__cpp_lib_destroying_delete) # if (__cplusplus >= 202002L) && (__cpp_impl_destroying_delete) # define __glibcxx_destroying_delete 201806L diff --git a/libstdc++-v3/include/std/format b/libstdc++-v3/include/std/format index 04fb23eb1367..46bd5d5ee6a0 100644 --- a/libstdc++-v3/include/std/format +++ b/libstdc++-v3/include/std/format @@ -5483,32 +5483,6 @@ namespace __format #endif #if __glibcxx_format_ranges // C++ >= 23 && HOSTED - // [format.range], formatting of ranges - // [format.range.fmtkind], variable template format_kind - enum class range_format { - disabled, - map, - set, - sequence, - string, - debug_string - }; - - /** @brief A constant determining how a range should be formatted. - * - * The primary template of `std::format_kind` cannot be instantiated. - * There is a partial specialization for input ranges and you can - * specialize the variable template for your own cv-unqualified types - * that satisfy the `ranges::input_range` concept. - * - * @since C++23 - */ - template - constexpr auto format_kind = []{ - static_assert(false, "cannot use primary template of 'std::format_kind'"); - return type_identity<_Rg>{}; - }(); - /// @cond undocumented template consteval range_format diff --git a/libstdc++-v3/include/std/optional b/libstdc++-v3/include/std/optional index a616dc07b107..2ae71f11e213 100644 --- a/libstdc++-v3/include/std/optional +++ b/libstdc++-v3/include/std/optional @@ -36,6 +36,7 @@ #define __glibcxx_want_freestanding_optional #define __glibcxx_want_optional +#define __glibcxx_want_optional_range_support #define __glibcxx_want_constrained_equality #include @@ -57,6 +58,11 @@ #if __cplusplus > 202002L # include #endif +#ifdef __cpp_lib_optional_range_support // C++ >= 26 +# include +# include +# include +#endif namespace std _GLIBCXX_VISIBILITY(default) { @@ -858,6 +864,10 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION public: using value_type = _Tp; +#ifdef __cpp_lib_optional_range_support // >= C++26 + using iterator = __gnu_cxx::__normal_iterator<_Tp*, optional>; + using const_iterator = __gnu_cxx::__normal_iterator; +#endif constexpr optional() noexcept { } @@ -1158,6 +1168,33 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION } } +#ifdef __cpp_lib_optional_range_support // >= C++26 + // Iterator support. + constexpr iterator begin() noexcept + { + return iterator( + this->_M_is_engaged() ? std::addressof(this->_M_get()) : nullptr + ); + } + + constexpr const_iterator begin() const noexcept + { + return const_iterator( + this->_M_is_engaged() ? std::addressof(this->_M_get()) : nullptr + ); + } + + constexpr iterator end() noexcept + { + return begin() + has_value(); + } + + constexpr const_iterator end() const noexcept + { + return begin() + has_value(); + } +#endif // __cpp_lib_optional_range_support + // Observers. constexpr const _Tp* operator->() const noexcept @@ -1772,6 +1809,16 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION template optional(_Tp) -> optional<_Tp>; #endif +#ifdef __cpp_lib_optional_range_support // >= C++26 + template + inline constexpr bool + ranges::enable_view> = true; + + template + inline constexpr auto + format_kind> = range_format::disabled; +#endif // __cpp_lib_optional_range_support + #undef _GLIBCXX_USE_CONSTRAINTS_FOR_OPTIONAL _GLIBCXX_END_NAMESPACE_VERSION diff --git a/libstdc++-v3/testsuite/20_util/optional/range.cc b/libstdc++-v3/testsuite/20_util/optional/range.cc new file mode 100644 index 000000000000..e77dc21e22b3 --- /dev/null +++ b/libstdc++-v3/testsuite/20_util/optional/range.cc @@ -0,0 +1,163 @@ +// { dg-do compile { target c++26 } } + +#include +#include +#include +#include +#include +#include +#include + +#include + +template +constexpr +void +test_range_concepts() +{ + static_assert(std::ranges::contiguous_range); + static_assert(std::ranges::sized_range); + static_assert(std::ranges::common_range); + static_assert(!std::ranges::borrowed_range); + + // an optional is not assignable, and therefore does not satisfy ranges::view + using T = typename O::value_type; + constexpr bool is_const_opt = std::is_const_v; + static_assert(std::ranges::view == !is_const_opt); + static_assert(std::ranges::viewable_range == !is_const_opt); +} + +template +constexpr +void +test_iterator_concepts() +{ + using T = typename O::value_type; + using iterator = typename O::iterator; + static_assert(std::contiguous_iterator); + static_assert(std::is_same_v::value_type, std::remove_cv_t>); + static_assert(std::is_same_v, std::remove_cv_t>); + static_assert(std::is_same_v::reference, T&>); + static_assert(std::is_same_v, T&>); + + using const_iterator = typename O::const_iterator; + static_assert(std::contiguous_iterator); + static_assert(std::is_same_v::value_type, std::remove_cv_t>); + static_assert(std::is_same_v, std::remove_cv_t>); + static_assert(std::is_same_v::reference, const T&>); + static_assert(std::is_same_v, const T&>); +} + +template +constexpr +void +test_empty() +{ + O empty; + VERIFY(!empty); + VERIFY(empty.begin() == empty.end()); + VERIFY(std::as_const(empty).begin() == std::as_const(empty).end()); + VERIFY(std::ranges::empty(empty)); + VERIFY(std::ranges::empty(std::as_const(empty))); + VERIFY(std::ranges::empty(empty | std::views::as_const)); + VERIFY(std::ranges::size(empty) == 0); + VERIFY(std::ranges::size(std::as_const(empty)) == 0); + + size_t count = 0; + for (const auto& x : empty) + ++count; + VERIFY(count == 0); +} + +template +constexpr +void +test_non_empty(const T& value) +{ + O non_empty = std::make_optional(value); + VERIFY(non_empty); + VERIFY(*non_empty == value); + VERIFY(non_empty.begin() != non_empty.end()); + VERIFY(non_empty.begin() < non_empty.end()); + VERIFY(std::as_const(non_empty).begin() != std::as_const(non_empty).end()); + VERIFY(std::as_const(non_empty).begin() < std::as_const(non_empty).end()); + VERIFY(!std::ranges::empty(non_empty)); + VERIFY(!std::ranges::empty(std::as_const(non_empty))); + VERIFY(!std::ranges::empty(non_empty | std::views::as_const)); + VERIFY(std::ranges::size(non_empty) == 1); + VERIFY(std::ranges::size(std::as_const(non_empty)) == 1); + + size_t count = 0; + for (const auto& x : non_empty) + ++count; + VERIFY(count == 1); + + if constexpr (!std::is_const_v) { + for (auto& x : non_empty) + x = T{}; + VERIFY(non_empty); + VERIFY(*non_empty == T{}); + } +} + +template +constexpr +void +test(const T& value) +{ + using O = std::optional; + test_range_concepts(); + test_iterator_concepts(); + test_empty(); + test_non_empty(value); + static_assert(!std::formattable); + static_assert(!std::formattable); + static_assert(std::format_kind == std::range_format::disabled); +} + +constexpr +void +range_chain_example() // from P3168 +{ + std::vector v{2, 3, 4, 5, 6, 7, 8, 9, 1}; + auto test = [](int i) -> std::optional { + switch(i) { + case 1: + case 3: + case 7: + case 9: + return i * 2; + default: + return {}; + } + }; + + auto result = v + | std::views::transform(test) + | std::views::filter([](auto x) { return bool(x); }) + | std::views::transform([](auto x){ return *x; }) + | std::ranges::to(); + + bool ok = result == std::vector{6, 14, 18, 2}; + VERIFY(ok); +} + +constexpr +bool +all_tests() +{ + test(42); + int i = 42; + test(&i); + test(std::string_view("test")); + test(std::vector{1, 2, 3, 4}); + test(std::optional(42)); + test(42); + + range_chain_example(); + + return true; +} + +static_assert(all_tests()); + diff --git a/libstdc++-v3/testsuite/20_util/optional/version.cc b/libstdc++-v3/testsuite/20_util/optional/version.cc index 657a3992422a..ba44aa525356 100644 --- a/libstdc++-v3/testsuite/20_util/optional/version.cc +++ b/libstdc++-v3/testsuite/20_util/optional/version.cc @@ -21,8 +21,17 @@ #endif #endif +#if __cplusplus > 202302L +# ifndef __cpp_lib_optional_range_support +# error "Feature test macro for optional range support is missing in " +# elif __cpp_lib_optional_range_support != 202406L +# error "Feature test macro for optional range support has wrong value for C++26 in " +# endif +#endif + #undef __cpp_lib_optional #undef __cpp_lib_freestanding_optional +#undef __cpp_lib_optional_range_support #include #if __cplusplus >= 202302L @@ -32,3 +41,12 @@ # error "Feature test macro for freestanding std::optional has wrong value in " #endif #endif + +#if __cplusplus > 202302L +# ifndef __cpp_lib_optional_range_support +# error "Feature test macro for optional range support is missing in " +# endif +# if __cpp_lib_optional_range_support != 202406L +# error "Feature test macro for optional range support has wrong value for C++26 in " +# endif +#endif