https://gcc.gnu.org/bugzilla/show_bug.cgi?id=128475
Bug ID: 128475
Summary: `bits/iterator_concepts.h` line 880~922: inconsistent
uses of dereference operator. (Maybe a potential LWG
Defect)
Product: gcc
Version: 17.0
Status: UNCONFIRMED
Severity: normal
Priority: P3
Component: libstdc++
Assignee: unassigned at gcc dot gnu.org
Reporter: songb432 at gmail dot com
Target Milestone: ---
Original code:
```
struct _IterSwap
{
private:
template<typename _Tp, typename _Up>
static consteval bool
_S_noexcept()
{
if constexpr (__adl_iswap<_Tp, _Up>)
return noexcept(iter_swap(std::declval<_Tp>(),
std::declval<_Up>()));
else if constexpr (indirectly_readable<_Tp>
&& indirectly_readable<_Up>
&& swappable_with<iter_reference_t<_Tp>, iter_reference_t<_Up>>)
return noexcept(ranges::swap(*std::declval<_Tp>(),
*std::declval<_Up>()));
else
return noexcept(*std::declval<_Tp>()
= __iswap::__iter_exchange_move(std::declval<_Up>(),
std::declval<_Tp>()));
}
public:
template<typename _Tp, typename _Up>
requires __adl_iswap<_Tp, _Up>
|| (indirectly_readable<remove_reference_t<_Tp>>
&& indirectly_readable<remove_reference_t<_Up>>
&& swappable_with<iter_reference_t<_Tp>, iter_reference_t<_Up>>)
|| (indirectly_movable_storable<_Tp, _Up>
&& indirectly_movable_storable<_Up, _Tp>)
constexpr void
operator()(_Tp&& __e1, _Up&& __e2) const
noexcept(_S_noexcept<_Tp, _Up>())
{
if constexpr (__adl_iswap<_Tp, _Up>)
iter_swap(static_cast<_Tp&&>(__e1), static_cast<_Up&&>(__e2));
else if constexpr (indirectly_readable<_Tp>
&& indirectly_readable<_Up>
&& swappable_with<iter_reference_t<_Tp>, iter_reference_t<_Up>>)
ranges::swap(*__e1, *__e2);
else
*__e1 = __iswap::__iter_exchange_move(__e2, __e1);
}
};
```
Maybe the standard wording is wrong, `iter_swap` should consider lvalue
dereference at every place. Or maybe cppreference is wrong, ``reference type of
E`` is ``decltype(*E)``, not ``iter_reference_t<decltype((E))>``.
Suggested fix (if the standard wording is wrong):
```
template<typename _Tp, typename _Up>
static consteval bool
_S_noexcept()
{
if constexpr (__adl_iswap<_Tp, _Up>)
return noexcept(iter_swap(std::declval<_Tp>(),
std::declval<_Up>()));
else if constexpr (indirectly_readable<_Tp>
&& indirectly_readable<_Up>
&& swappable_with<iter_reference_t<_Tp>, iter_reference_t<_Up>>)
return noexcept(ranges::swap(*std::declval<_Tp&>(),
*std::declval<_Up&>()));
else
return noexcept(*std::declval<_Tp&>()
= __iswap::__iter_exchange_move(std::declval<_Up&>(),
std::declval<_Tp&>()));
}
```
So that each `noexcept` in `_S_noexcept` are consistent with the cases in
`operator()`.
Or, if cppreference is wrong, ``reference types of E1 and E2`` are
``decltype(*E1)`` and ``decltype(*E2)``. Then the correct fix would be:
```
static consteval bool
_S_noexcept()
{
if constexpr (__adl_iswap<_Tp, _Up>)
return noexcept(iter_swap(std::declval<_Tp>(),
std::declval<_Up>()));
else if constexpr (indirectly_readable<_Tp>
&& indirectly_readable<_Up>
&& swappable_with<decltype(*std::declval<_Tp>()),
decltype(*std::declval<_Up>())>)
return noexcept(ranges::swap(*std::declval<_Tp>(),
*std::declval<_Up>()));
else
return noexcept(*std::declval<_Tp>()
= __iswap::__iter_exchange_move(std::declval<_Up>(),
std::declval<_Tp>()));
}
```
and
```
constexpr void
operator()(_Tp&& __e1, _Up&& __e2) const
noexcept(_S_noexcept<_Tp, _Up>())
{
if constexpr (__adl_iswap<_Tp, _Up>)
iter_swap(static_cast<_Tp&&>(__e1), static_cast<_Up&&>(__e2));
else if constexpr (indirectly_readable<_Tp>
&& indirectly_readable<_Up>
&& swappable_with<decltype(*std::declval<_Tp>()),
decltype(*std::declval<_Up>())>)
ranges::swap(*static_cast<_Tp&&>(__e1),
*static_cast<_Up&&>(__e2));
else
*static_cast<_Tp&&>(__e1)
= __iswap::__iter_exchange_move(static_cast<_Up&&>(__e2),
static_cast<_Tp&&>(__e1));
}
```