Skip to content

[clang-tidy] misc-unconventional-assign-operator false positive for classes satisfying std::indirectly_writable #146089

Open
@justusranvier

Description

@justusranvier

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 of indirectly_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); }
};

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions