Open
Description
The std::indirectly_writable concept which is necessary to create proxy references for iterators that are usable in range algorithms requires const-qualified assignment operators.
Those operators currently do not meet clang-tidy's of "conventional", which predates C++20 and range algorithms.
From cppreference:
The required expressions with
const_cast
prevent indirectly_readable objects with prvalue reference types from satisfying the syntactic requirements ofindirectly_writable
by accident, while permitting proxy references to continue to work as long as their constness is shallow. See Ranges TS issue 381.
An example of a proxy type with shallow constness which is currently flagged by clang-tidy:
struct Proxy {
auto GetA() const -> A&
{
return soa_->vector_of_a_[index_];
}
auto GetB() const -> B&
{
return soa_->vector_of_b_[index_];
}
auto swap(Proxy& rhs) -> void
{
indirect_swap(rhs);
std::ranges::swap(soa_, rhs.soa_);
std::ranges::swap(index_, rhs.index_);
}
Proxy(SoA* soa, std::size_t index)
: soa_(soa)
, index_(index)
{
}
Proxy(Proxy const& rhs) = default;
Proxy(Proxy&& rhs) = default;
auto operator=(Proxy const& rhs) -> Proxy&
{
indirect_assign(rhs);
soa_ = rhs.soa_;
index_ = rhs.index_;
return *this;
}
auto operator=(Proxy&& rhs) -> Proxy&
{
swap(rhs);
return *this;
}
// NOLINTBEGIN(misc-unconventional-assign-operator)
auto operator=(Proxy const& rhs) const -> Proxy const&
{
indirect_assign(rhs);
return *this;
}
auto operator=(Proxy&& rhs) const -> Proxy const&
{
indirect_swap(rhs);
return *this;
}
// NOLINTEND(misc-unconventional-assign-operator)
~Proxy() = default;
private:
SoA* soa_;
std::size_t index_;
auto indirect_assign(Proxy const& rhs) const noexcept -> void
{
GetA() = rhs.GetA();
GetB() = rhs.GetB();
}
auto indirect_swap(Proxy& rhs) const noexcept -> void
{
std::ranges::swap(GetA(), rhs.GetA());
std::ranges::swap(GetB(), rhs.GetB());
}
friend auto swap(Proxy& lhs, Proxy& rhs) noexcept -> void { lhs.swap(rhs); }
};