Hello, On 12/06/2025 13:09, Jonathan Wakely wrote:
Reviewed and approved on the forge - thanks.
Thank you. I've committed it as r16-1487-g774ae8734f0e19, also attached. Cheers, -- Giuseppe D'Angelo
From 774ae8734f0e199a8c6d29dd8c186b893385470b Mon Sep 17 00:00:00 2001 From: Giuseppe D'Angelo <giuseppe.dang...@kdab.com> Date: Mon, 9 Jun 2025 23:13:21 +0200 Subject: [PATCH] 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 <format> when including <optional>, I've isolated format_kind and some supporting code into <bits/formatfwd.h> 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 <format>. * 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. Signed-off-by: Giuseppe D'Angelo <giuseppe.dang...@kdab.com> --- libstdc++-v3/include/bits/formatfwd.h | 26 +++ libstdc++-v3/include/bits/version.def | 8 + libstdc++-v3/include/bits/version.h | 10 ++ libstdc++-v3/include/std/format | 26 --- libstdc++-v3/include/std/optional | 47 +++++ .../testsuite/20_util/optional/range.cc | 163 ++++++++++++++++++ .../testsuite/20_util/optional/version.cc | 18 ++ 7 files changed, 272 insertions(+), 26 deletions(-) create mode 100644 libstdc++-v3/testsuite/20_util/optional/range.cc diff --git a/libstdc++-v3/include/bits/formatfwd.h b/libstdc++-v3/include/bits/formatfwd.h index 777e6290f74..314b55d50bc 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<formattable<const _Tp, _CharT>, 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<typename _Rg> + 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 7cf62e989aa..880586e9126 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 9f4cf9a3425..4300adb2276 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 04fb23eb136..46bd5d5ee6a 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<typename _Rg> - constexpr auto format_kind = []{ - static_assert(false, "cannot use primary template of 'std::format_kind'"); - return type_identity<_Rg>{}; - }(); - /// @cond undocumented template<typename _Tp> consteval range_format diff --git a/libstdc++-v3/include/std/optional b/libstdc++-v3/include/std/optional index a616dc07b10..2ae71f11e21 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 <bits/version.h> @@ -57,6 +58,11 @@ #if __cplusplus > 202002L # include <concepts> #endif +#ifdef __cpp_lib_optional_range_support // C++ >= 26 +# include <bits/formatfwd.h> +# include <bits/ranges_base.h> +# include <bits/stl_iterator.h> +#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<const _Tp*, optional>; +#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 <typename _Tp> optional(_Tp) -> optional<_Tp>; #endif +#ifdef __cpp_lib_optional_range_support // >= C++26 + template<typename _Tp> + inline constexpr bool + ranges::enable_view<optional<_Tp>> = true; + + template<typename _Tp> + inline constexpr auto + format_kind<optional<_Tp>> = 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 00000000000..e77dc21e22b --- /dev/null +++ b/libstdc++-v3/testsuite/20_util/optional/range.cc @@ -0,0 +1,163 @@ +// { dg-do compile { target c++26 } } + +#include <concepts> +#include <format> +#include <iterator> +#include <optional> +#include <ranges> +#include <string_view> +#include <vector> + +#include <testsuite_hooks.h> + +template<typename O> +constexpr +void +test_range_concepts() +{ + static_assert(std::ranges::contiguous_range<O>); + static_assert(std::ranges::sized_range<O>); + static_assert(std::ranges::common_range<O>); + static_assert(!std::ranges::borrowed_range<O>); + + // an optional<const T> 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<T>; + static_assert(std::ranges::view<O> == !is_const_opt); + static_assert(std::ranges::viewable_range<O> == !is_const_opt); +} + +template<typename O> +constexpr +void +test_iterator_concepts() +{ + using T = typename O::value_type; + using iterator = typename O::iterator; + static_assert(std::contiguous_iterator<iterator>); + static_assert(std::is_same_v<typename std::iterator_traits<iterator>::value_type, std::remove_cv_t<T>>); + static_assert(std::is_same_v<std::iter_value_t<iterator>, std::remove_cv_t<T>>); + static_assert(std::is_same_v<typename std::iterator_traits<iterator>::reference, T&>); + static_assert(std::is_same_v<std::iter_reference_t<iterator>, T&>); + + using const_iterator = typename O::const_iterator; + static_assert(std::contiguous_iterator<const_iterator>); + static_assert(std::is_same_v<typename std::iterator_traits<const_iterator>::value_type, std::remove_cv_t<T>>); + static_assert(std::is_same_v<std::iter_value_t<const_iterator>, std::remove_cv_t<T>>); + static_assert(std::is_same_v<typename std::iterator_traits<const_iterator>::reference, const T&>); + static_assert(std::is_same_v<std::iter_reference_t<const_iterator>, const T&>); +} + +template<typename O> +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<typename O, typename T> +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<typename O::value_type>) { + for (auto& x : non_empty) + x = T{}; + VERIFY(non_empty); + VERIFY(*non_empty == T{}); + } +} + +template<typename T> +constexpr +void +test(const T& value) +{ + using O = std::optional<T>; + test_range_concepts<O>(); + test_iterator_concepts<O>(); + test_empty<O>(); + test_non_empty<O>(value); + static_assert(!std::formattable<O, char>); + static_assert(!std::formattable<O, wchar_t>); + static_assert(std::format_kind<O> == std::range_format::disabled); +} + +constexpr +void +range_chain_example() // from P3168 +{ + std::vector<int> v{2, 3, 4, 5, 6, 7, 8, 9, 1}; + auto test = [](int i) -> std::optional<int> { + 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<std::vector>(); + + bool ok = result == std::vector<int>{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<int>{1, 2, 3, 4}); + test(std::optional<int>(42)); + test<const int>(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 657a3992422..ba44aa52535 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 <version>" +# elif __cpp_lib_optional_range_support != 202406L +# error "Feature test macro for optional range support has wrong value for C++26 in <version>" +# endif +#endif + #undef __cpp_lib_optional #undef __cpp_lib_freestanding_optional +#undef __cpp_lib_optional_range_support #include <optional> #if __cplusplus >= 202302L @@ -32,3 +41,12 @@ # error "Feature test macro for freestanding std::optional has wrong value in <optional>" #endif #endif + +#if __cplusplus > 202302L +# ifndef __cpp_lib_optional_range_support +# error "Feature test macro for optional range support is missing in <optional>" +# endif +# if __cpp_lib_optional_range_support != 202406L +# error "Feature test macro for optional range support has wrong value for C++26 in <optional>" +# endif +#endif -- 2.43.0
smime.p7s
Description: S/MIME Cryptographic Signature