libstdc++: Implement P0849R8 auto(x) library changes

This implements the library changes in P0849R8 "auto(x): decay-copy
in the language" which consist of replacing most uses of the
exposition-only function decay-copy with auto(x) throughout the library
wording.  We implement this as a DR against C++20 since there should be
no behavior change in practice (especially in light of LWG 3724 which
makes decay-copy SFINAE-friendly).

The main difference between decay-copy and auto(x) is that decay-copy
materializes its argument unlike auto(x), and so the latter is a no-op
when its argument is a prvalue.  Effectively the former could introduce
an unnecessary move constructor call in some contexts.  In C++20 and
earlier we could emulate auto(x) with decay_t<decltype((x))>(x).

After this paper the only remaining uses of decay-copy in the standard
are in the specification of some range adaptors.  In our implementation
of those range adaptors I believe decay-copy is already implied which is
why we don't use __decay_copy explicitly there.  So since it's apparently
no longer needed this patch goes ahead and removes __decay_copy.

libstdc++-v3/ChangeLog:

	* include/bits/c++config (_GLIBCXX_AUTO_CAST): Define.
	* include/bits/iterator_concepts.h (_Decay_copy, __decay_copy):
	Remove.
	(__member_begin, __adl_begin): Use _GLIBCXX_AUTO_CAST instead of
	__decay_copy as per P0849R8.
	* include/bits/ranges_base.h (_Begin): Likewise.
	(__member_end, __adl_end, _End): Likewise.
	(__member_rbegin, __adl_rbegin, _RBegin): Likewise.
	(__member_rend, __adl_rend, _Rend): Likewise.
	(__member_size, __adl_size, _Size): Likewise.
	(_Data): Likewise.

Reviewed-by: Jonathan Wakely <jwakely@redhat.com>
This commit is contained in:
Patrick Palka
2025-06-04 14:55:40 -04:00
parent 0292e066d5
commit e73a6d9827
3 changed files with 28 additions and 31 deletions

View File

@@ -273,6 +273,12 @@
#define _GLIBCXX_NOEXCEPT_QUAL
#endif
#if __cpp_auto_cast
# define _GLIBCXX_AUTO_CAST(X) auto(X)
#else
# define _GLIBCXX_AUTO_CAST(X) ::std::__decay_t<decltype((X))>(X)
#endif
// Macro for extern template, ie controlling template linkage via use
// of extern keyword on template declaration. As documented in the g++
// manual, it inhibits all implicit instantiations and is used

View File

@@ -1022,19 +1022,10 @@ namespace ranges
{
using std::__detail::__class_or_enum;
struct _Decay_copy final
{
template<typename _Tp>
constexpr decay_t<_Tp>
operator()(_Tp&& __t) const
noexcept(is_nothrow_convertible_v<_Tp, decay_t<_Tp>>)
{ return std::forward<_Tp>(__t); }
} inline constexpr __decay_copy{};
template<typename _Tp>
concept __member_begin = requires(_Tp& __t)
{
{ __decay_copy(__t.begin()) } -> input_or_output_iterator;
{ _GLIBCXX_AUTO_CAST(__t.begin()) } -> input_or_output_iterator;
};
// Poison pill so that unqualified lookup doesn't find std::begin.
@@ -1044,7 +1035,7 @@ namespace ranges
concept __adl_begin = __class_or_enum<remove_reference_t<_Tp>>
&& requires(_Tp& __t)
{
{ __decay_copy(begin(__t)) } -> input_or_output_iterator;
{ _GLIBCXX_AUTO_CAST(begin(__t)) } -> input_or_output_iterator;
};
// Simplified version of std::ranges::begin that only supports lvalues,

View File

@@ -119,9 +119,9 @@ namespace ranges
if constexpr (is_array_v<remove_reference_t<_Tp>>)
return true;
else if constexpr (__member_begin<_Tp>)
return noexcept(__decay_copy(std::declval<_Tp&>().begin()));
return noexcept(_GLIBCXX_AUTO_CAST(std::declval<_Tp&>().begin()));
else
return noexcept(__decay_copy(begin(std::declval<_Tp&>())));
return noexcept(_GLIBCXX_AUTO_CAST(begin(std::declval<_Tp&>())));
}
public:
@@ -146,7 +146,7 @@ namespace ranges
template<typename _Tp>
concept __member_end = requires(_Tp& __t)
{
{ __decay_copy(__t.end()) } -> sentinel_for<__range_iter_t<_Tp>>;
{ _GLIBCXX_AUTO_CAST(__t.end()) } -> sentinel_for<__range_iter_t<_Tp>>;
};
// Poison pill so that unqualified lookup doesn't find std::end.
@@ -156,7 +156,7 @@ namespace ranges
concept __adl_end = __class_or_enum<remove_reference_t<_Tp>>
&& requires(_Tp& __t)
{
{ __decay_copy(end(__t)) } -> sentinel_for<__range_iter_t<_Tp>>;
{ _GLIBCXX_AUTO_CAST(end(__t)) } -> sentinel_for<__range_iter_t<_Tp>>;
};
struct _End
@@ -169,9 +169,9 @@ namespace ranges
if constexpr (is_bounded_array_v<remove_reference_t<_Tp>>)
return true;
else if constexpr (__member_end<_Tp>)
return noexcept(__decay_copy(std::declval<_Tp&>().end()));
return noexcept(_GLIBCXX_AUTO_CAST(std::declval<_Tp&>().end()));
else
return noexcept(__decay_copy(end(std::declval<_Tp&>())));
return noexcept(_GLIBCXX_AUTO_CAST(end(std::declval<_Tp&>())));
}
public:
@@ -196,7 +196,7 @@ namespace ranges
template<typename _Tp>
concept __member_rbegin = requires(_Tp& __t)
{
{ __decay_copy(__t.rbegin()) } -> input_or_output_iterator;
{ _GLIBCXX_AUTO_CAST(__t.rbegin()) } -> input_or_output_iterator;
};
void rbegin() = delete;
@@ -205,7 +205,7 @@ namespace ranges
concept __adl_rbegin = __class_or_enum<remove_reference_t<_Tp>>
&& requires(_Tp& __t)
{
{ __decay_copy(rbegin(__t)) } -> input_or_output_iterator;
{ _GLIBCXX_AUTO_CAST(rbegin(__t)) } -> input_or_output_iterator;
};
template<typename _Tp>
@@ -223,9 +223,9 @@ namespace ranges
_S_noexcept()
{
if constexpr (__member_rbegin<_Tp>)
return noexcept(__decay_copy(std::declval<_Tp&>().rbegin()));
return noexcept(_GLIBCXX_AUTO_CAST(std::declval<_Tp&>().rbegin()));
else if constexpr (__adl_rbegin<_Tp>)
return noexcept(__decay_copy(rbegin(std::declval<_Tp&>())));
return noexcept(_GLIBCXX_AUTO_CAST(rbegin(std::declval<_Tp&>())));
else
{
if constexpr (noexcept(_End{}(std::declval<_Tp&>())))
@@ -258,7 +258,7 @@ namespace ranges
template<typename _Tp>
concept __member_rend = requires(_Tp& __t)
{
{ __decay_copy(__t.rend()) }
{ _GLIBCXX_AUTO_CAST(__t.rend()) }
-> sentinel_for<decltype(_RBegin{}(std::forward<_Tp>(__t)))>;
};
@@ -268,7 +268,7 @@ namespace ranges
concept __adl_rend = __class_or_enum<remove_reference_t<_Tp>>
&& requires(_Tp& __t)
{
{ __decay_copy(rend(__t)) }
{ _GLIBCXX_AUTO_CAST(rend(__t)) }
-> sentinel_for<decltype(_RBegin{}(std::forward<_Tp>(__t)))>;
};
@@ -280,9 +280,9 @@ namespace ranges
_S_noexcept()
{
if constexpr (__member_rend<_Tp>)
return noexcept(__decay_copy(std::declval<_Tp&>().rend()));
return noexcept(_GLIBCXX_AUTO_CAST(std::declval<_Tp&>().rend()));
else if constexpr (__adl_rend<_Tp>)
return noexcept(__decay_copy(rend(std::declval<_Tp&>())));
return noexcept(_GLIBCXX_AUTO_CAST(rend(std::declval<_Tp&>())));
else
{
if constexpr (noexcept(_Begin{}(std::declval<_Tp&>())))
@@ -316,7 +316,7 @@ namespace ranges
concept __member_size = !disable_sized_range<remove_cvref_t<_Tp>>
&& requires(_Tp& __t)
{
{ __decay_copy(__t.size()) } -> __detail::__is_integer_like;
{ _GLIBCXX_AUTO_CAST(__t.size()) } -> __detail::__is_integer_like;
};
void size() = delete;
@@ -326,7 +326,7 @@ namespace ranges
&& !disable_sized_range<remove_cvref_t<_Tp>>
&& requires(_Tp& __t)
{
{ __decay_copy(size(__t)) } -> __detail::__is_integer_like;
{ _GLIBCXX_AUTO_CAST(size(__t)) } -> __detail::__is_integer_like;
};
template<typename _Tp>
@@ -351,9 +351,9 @@ namespace ranges
if constexpr (is_bounded_array_v<remove_reference_t<_Tp>>)
return true;
else if constexpr (__member_size<_Tp>)
return noexcept(__decay_copy(std::declval<_Tp&>().size()));
return noexcept(_GLIBCXX_AUTO_CAST(std::declval<_Tp&>().size()));
else if constexpr (__adl_size<_Tp>)
return noexcept(__decay_copy(size(std::declval<_Tp&>())));
return noexcept(_GLIBCXX_AUTO_CAST(size(std::declval<_Tp&>())));
else if constexpr (__sentinel_size<_Tp>)
return noexcept(_End{}(std::declval<_Tp&>())
- _Begin{}(std::declval<_Tp&>()));
@@ -463,7 +463,7 @@ namespace ranges
template<typename _Tp>
concept __member_data = requires(_Tp& __t)
{
{ __decay_copy(__t.data()) } -> __pointer_to_object;
{ _GLIBCXX_AUTO_CAST(__t.data()) } -> __pointer_to_object;
};
template<typename _Tp>
@@ -477,7 +477,7 @@ namespace ranges
_S_noexcept()
{
if constexpr (__member_data<_Tp>)
return noexcept(__decay_copy(std::declval<_Tp&>().data()));
return noexcept(_GLIBCXX_AUTO_CAST(std::declval<_Tp&>().data()));
else
return noexcept(_Begin{}(std::declval<_Tp&>()));
}