Compare boost intrusive_ptr with different const-ness

116 views Asked by At

I would like to compare boost::intrusive_ptr instances with different const-ness of underlying type, but unfortunately it doesn't work from the box if you uncomment the line:

#include <boost/intrusive_ptr.hpp>
#include <memory>

struct S {};

void intrusive_ptr_release(const S*){}

int main() { 

    boost::intrusive_ptr<const S> boost_const_ptr;
    boost::intrusive_ptr<S> boost_ptr;
    // if(boost_const_ptr < boost_ptr) {} // <------ here
    
    const S* s_const_ptr;
    S* s_ptr;
    if(s_const_ptr < s_ptr) {}

    std::unique_ptr<const S> u_const_ptr;
    std::unique_ptr<S> u_ptr;
    if(u_const_ptr < u_ptr) {}
}

The error will be:

no match for 'operator<' (operand types are 'boost::intrusive_ptr<const S>' and 'boost::intrusive_ptr<S>')

I can compare raw pointers, or even std::unique_ptrs, but not boost::intrusive_ptr. Is there a reason for it, or just an oversight?

2

There are 2 answers

0
Caleth On

A possible reason is that equivalent semantics to shared_ptr < would be impossible in general. Other smart pointer types don't have to deal with the possibility of heterogenous comparison1, but shared_ptr, weak_ptr and intrusive_ptr do.

template<class T, class U>
bool operator<(shared_ptr<T> const & a, shared_ptr<U> const & b) noexcept;

Returns:

An unspecified value such that

  • operator< is a strict weak ordering as described in section [lib.alg.sorting] of the C++ standard;

  • under the equivalence relation defined by operator<, !(a < b) && !(b < a), two shared_ptr instances are equivalent if and only if they share ownership or are both empty.

shared_ptr is able to do that by comparing addresses of the ref count. intrusive_ptr can't do that, because it doesn't have pointer access to the ref count.

  1. E.g. when you compare a shared_ptr<Base> to a shared_ptr<Derived> they might share ownership of the same object, which is impossible for unique_ptr<Base> and unique_ptr<Derived>.
0
sehe On

You might add an overload for your types to be found via ADL:

#include <boost/intrusive_ptr.hpp>
#include <memory>

namespace MyStuff {
    struct S {};

    void intrusive_ptr_release(S const*) {}

    template <typename T, typename U, typename V = std::remove_cv_t<T>>
        requires std::is_same_v<V, std::remove_cv_t<U>>
    constexpr bool operator<(boost::intrusive_ptr<T> const& a, boost::intrusive_ptr<U> const& b) {
        return std::less<V const*>{}(a.get(), b.get());
    }
} // namespace MyStuff

Note how the ADL overload takes precedence so we don't need to SFINAE constrain for the case T === U to avoid creating ambiguous overloads:

Live On Coliru

int main() {
    using MyStuff::S;
    {
        boost::intrusive_ptr<S const> cp;
        boost::intrusive_ptr<S>       p;

        if (cp < p) { }
        if (cp < cp) { }
        if (p < p) { }
    }

    {
        S const* cp = nullptr;
        S*       p  = nullptr;

        if (cp < p) { }
        if (cp < cp) { }
        if (p < p) { }
    }

    {
        std::unique_ptr<S const> cp;
        std::unique_ptr<S>       p;

        if (cp < p) { }
        if (cp < cp) { }
        if (p < p) { }
    }
}

BONUS

A c++17 version:

template <typename T, typename U, typename V = std::remove_cv_t<T>>
constexpr auto operator<(boost::intrusive_ptr<T> const& a, boost::intrusive_ptr<U> const& b)
    -> std::enable_if_t<std::is_same_v<V, std::remove_cv_t<U>>, bool> {
    return std::less<V const*>{}(a.get(), b.get());
}

Pure c++11 version:

template <typename T, typename U, typename V = typename std::remove_cv<T>::type>
constexpr auto operator<(boost::intrusive_ptr<T> const& a, boost::intrusive_ptr<U> const& b)
    -> typename std::enable_if<std::is_same<V, typename std::remove_cv<U>::type>::value, bool>::type {
    return std::less<V const*>{}(a.get(), b.get());
}