Hello, On 10/06/2025 16:32, Jonathan Wakely wrote:
Whops, I again addedstd::string to a constexpr test, so this will fail on the old ABI. Should I change it to e.g.std::vector, or should I keep it and add a `{ dg-require-effective-target cxx11_abi }` directive?Would string_view work instead? If not, please change it to vector.
Added both and removed string. New patch attached. Thank you, -- Giuseppe D'Angelo
From 767cee328aa59aaae84f72358fa2c5eb0293a5ad 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>; adjust inclusions. * 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. Signed-off-by: Giuseppe D'Angelo <giuseppe.dang...@kdab.com> --- libstdc++-v3/include/bits/formatfwd.h | 64 +++++++ libstdc++-v3/include/bits/version.def | 8 + libstdc++-v3/include/bits/version.h | 10 ++ libstdc++-v3/include/std/format | 61 ------- libstdc++-v3/include/std/optional | 47 +++++ .../testsuite/20_util/optional/range.cc | 160 ++++++++++++++++++ 6 files changed, 289 insertions(+), 61 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..f8197cbda72 100644 --- a/libstdc++-v3/include/bits/formatfwd.h +++ b/libstdc++-v3/include/bits/formatfwd.h @@ -40,7 +40,10 @@ #include <concepts> #include <type_traits> #if __glibcxx_format_ranges // C++ >= 23 && HOSTED +# include <tuple> +# include <bits/stl_pair.h> // __is_pair # include <bits/ranges_base.h> // input_range, range_reference_t +# include <bits/utility.h> // tuple_size_v #endif namespace std _GLIBCXX_VISIBILITY(default) @@ -126,6 +129,11 @@ namespace __format __f.set_debug_format(); }; } // namespace __format + + template<typename _Tp, template<typename...> class _Class> + constexpr bool __is_specialization_of = false; + template<template<typename...> class _Class, typename... _Args> + constexpr bool __is_specialization_of<_Class<_Args...>, _Class> = true; /// @endcond #if __glibcxx_format_ranges // C++ >= 23 && HOSTED @@ -162,6 +170,62 @@ 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>{}; + }(); + + /// @cond undocumented + template<typename _Tp> + consteval range_format + __fmt_kind() + { + using _Ref = ranges::range_reference_t<_Tp>; + if constexpr (is_same_v<remove_cvref_t<_Ref>, _Tp>) + return range_format::disabled; + else if constexpr (requires { typename _Tp::key_type; }) + { + if constexpr (requires { typename _Tp::mapped_type; }) + { + using _Up = remove_cvref_t<_Ref>; + if constexpr (__is_pair<_Up>) + return range_format::map; + else if constexpr (__is_specialization_of<_Up, tuple>) + if constexpr (tuple_size_v<_Up> == 2) + return range_format::map; + } + return range_format::set; + } + else + return range_format::sequence; + } + /// @endcond + + /// A constant determining how a range should be formatted. + template<ranges::input_range _Rg> requires same_as<_Rg, remove_cvref_t<_Rg>> + constexpr range_format format_kind<_Rg> = __fmt_kind<_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 9ab22cc519f..f864ea11bae 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 371a7ba3b1a..1611132cf21 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 b4929d5ae59..0a7b915f39a 100644 --- a/libstdc++-v3/include/std/format +++ b/libstdc++-v3/include/std/format @@ -412,11 +412,6 @@ namespace __format }; /// @cond undocumented - template<typename _Tp, template<typename...> class _Class> - constexpr bool __is_specialization_of = false; - template<template<typename...> class _Class, typename... _Args> - constexpr bool __is_specialization_of<_Class<_Args...>, _Class> = true; - namespace __format { // pre: first != last @@ -5472,62 +5467,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 - __fmt_kind() - { - using _Ref = ranges::range_reference_t<_Tp>; - if constexpr (is_same_v<remove_cvref_t<_Ref>, _Tp>) - return range_format::disabled; - else if constexpr (requires { typename _Tp::key_type; }) - { - if constexpr (requires { typename _Tp::mapped_type; }) - { - using _Up = remove_cvref_t<_Ref>; - if constexpr (__is_pair<_Up>) - return range_format::map; - else if constexpr (__is_specialization_of<_Up, tuple>) - if constexpr (tuple_size_v<_Up> == 2) - return range_format::map; - } - return range_format::set; - } - else - return range_format::sequence; - } - /// @endcond - - /// A constant determining how a range should be formatted. - template<ranges::input_range _Rg> requires same_as<_Rg, remove_cvref_t<_Rg>> - constexpr range_format format_kind<_Rg> = __fmt_kind<_Rg>(); - /// @cond undocumented namespace __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..0d9bed5f167 --- /dev/null +++ b/libstdc++-v3/testsuite/20_util/optional/range.cc @@ -0,0 +1,160 @@ +// { dg-do compile { target c++26 } } + +#include <concepts> +#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::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()); + -- 2.43.0
smime.p7s
Description: S/MIME Cryptographic Signature