Search code examples
c++stlcontainersconst-correctness

How to pass a container of unique_ptrs where container, ptrs and objects are not modifiable?


I have a container vector that has std::unique_ptr of some type. I want to return that container, but also want to enforce that I don't want the container, pointer or the object pointed at to be modifiable. I also don't want to make some paralel copy of this object. My alias type would be something like:

using container_t = vector<std::unique_ptr<my_type_t>>

So I'm thinking that I could make another alias like this:

using const_container_t = const vector<std::unique_ptr<const my_type_t>>

and do a reinterpret_cast for my getter:

const_container_t& encompassing_type::get_container() const
{
  return reinterpret_cast<const_container_t&>(m_container);
}

I'm thinking that this should work, but I'm wondering if there are any gotchas that I'm not seeing, or if there is some other better way of doing this.

I would also imagine that this might result in duplicate binary code in the final build, but as these are most likely inlined anyway, that shouldn't be an issue.


Solution

  • I didn't want to include boost and span wouldn't work, because as @Jens pointed out, a unique_ptr doesn't propagate the cv qualifiers. Also, even if I did include boost, I wouldn't be able to get an actual object reference for each item in the vector, which I would need to allow me to compare relative locations of the object with others in the container.

    So I opted instead for writing a wrapper over std::unique_ptr which will propagate the cv qualifiers.

    The following is an excerpt from my enable_if.h file, which I use for the comparison operators to limit how many times I have to write them:

    namespace detail
    {
        // Reason to use an enum class rather than just an int is so as to ensure
        // there will not be any clashes resulting in an ambiguous overload.
        enum class enabler
        {
            enabled
        };
    }
    #define ENABLE_IF(...) std::enable_if_t<__VA_ARGS__, detail::enabler> = detail::enabler::enabled
    #define ENABLE_IF_DEFINITION(...) std::enable_if_t<__VA_ARGS__, detail::enabler>
    

    Here is my implementation of c++20's std::remove_cvref_t:

    template <typename T>
    using remove_cvref_t = std::remove_cv_t<std::remove_reference_t<T>>;
    

    And here is the wrapped unique ptr:

    template <typename T, typename D = std::default_delete<T>>
    class unique_ptr_propagate_cv;
    
    namespace detail
    {
        template <typename T, typename D>
        std::unique_ptr<T, D> const& get_underlying_unique_ptr(unique_ptr_propagate_cv<T, D> const& object)
        {
            return object.ptr;
        }
    }
    
    template <typename T, typename D>
    class unique_ptr_propagate_cv
    {
        template <typename T_, typename D_>
        friend std::unique_ptr<T_, D_> const& detail::get_underlying_unique_ptr<T_, D_>(unique_ptr_propagate_cv<T_, D_> const&);
    
        using base = std::unique_ptr<T, D>;
        base ptr;
    public:
        template <typename...Ts>
        unique_ptr_propagate_cv(Ts&&...args) noexcept : ptr(std::forward<Ts>(args)...) {}
    
        using element_type           = typename base::element_type;
        using deleter_type           = typename base::deleter_type;
    
        using pointer                = element_type                *;
        using pointer_const          = element_type const          *;
        using pointer_volatile       = element_type       volatile *;
        using pointer_const_volatile = element_type const volatile *;
    
        using reference                = element_type                &;
        using reference_const          = element_type const          &;
        using reference_volatile       = element_type       volatile &;
        using reference_const_volatile = element_type const volatile &;
    
        pointer                get()                noexcept { return ptr.get(); }
        pointer_const          get() const          noexcept { return ptr.get(); }
        pointer_volatile       get()       volatile noexcept { return ptr.get(); }
        pointer_const_volatile get() const volatile noexcept { return ptr.get(); }
    
        pointer                operator->()                noexcept { return ptr.get(); }
        pointer_const          operator->() const          noexcept { return ptr.get(); }
        pointer_volatile       operator->()       volatile noexcept { return ptr.get(); }
        pointer_const_volatile operator->() const volatile noexcept { return ptr.get(); }
    
        reference                operator[](size_t index)                noexcept { return ptr.operator[](index); }
        reference_const          operator[](size_t index) const          noexcept { return ptr.operator[](index); }
        reference_volatile       operator[](size_t index)       volatile noexcept { return ptr.operator[](index); }
        reference_const_volatile operator[](size_t index) const volatile noexcept { return ptr.operator[](index); }
    
        reference                operator*()                noexcept { return ptr.operator*(); }
        reference_const          operator*() const          noexcept { return ptr.operator*(); }
        reference_volatile       operator*()       volatile noexcept { return ptr.operator*(); }
        reference_const_volatile operator*() const volatile noexcept { return ptr.operator*(); }
    
        template <typename T_>
        unique_ptr_propagate_cv& operator=(T_&& rhs)
        {
            return static_cast<unique_ptr_propagate_cv&>(ptr.operator=(std::forward<T_>(rhs)));
        }
    
        decltype(auto) get_deleter()            const noexcept { return ptr.get_deleter(); }
                       operator bool()          const noexcept { return ptr.operator bool(); }
        decltype(auto) reset(pointer ptr = pointer()) noexcept {        get_base_nonconst().reset(ptr); }
        decltype(auto) release()                      noexcept { return get_base_nonconst().release();  }
    
    };
    
    template <typename T>
    struct is_unique_ptr_propagate_cv : std::false_type {};
    
    template <typename T, typename D>
    struct is_unique_ptr_propagate_cv<unique_ptr_propagate_cv<T, D>> : std::true_type {};
    
    namespace detail
    {
        inline nullptr_t const& get_underlying_unique_ptr(nullptr_t const& object)
        {
            return object;
        }
    
        template <typename T, typename D>
        std::unique_ptr<T, D> const& get_underlying_unique_ptr(std::unique_ptr<T, D> const& object)
        {
            return object;
        }
    }
    
    template <typename L, typename R
        , ENABLE_IF(
               is_unique_ptr_propagate_cv<remove_cvref_t<L>>::value
            || is_unique_ptr_propagate_cv<remove_cvref_t<R>>::value
        )
    >
    bool operator==(L&& lhs, R&& rhs) noexcept
    {
        return detail::get_underlying_unique_ptr(std::forward<L>(lhs))
            == detail::get_underlying_unique_ptr(std::forward<R>(rhs));
    }
    
    template <typename L, typename R
        , ENABLE_IF(
               is_unique_ptr_propagate_cv<remove_cvref_t<L>>::value
            || is_unique_ptr_propagate_cv<remove_cvref_t<R>>::value
        )
    >
    auto operator!=(L&& lhs, R&& rhs) noexcept
    {
        return detail::get_underlying_unique_ptr(std::forward<L>(lhs))
            != detail::get_underlying_unique_ptr(std::forward<R>(rhs));
    }
    
    template <typename L, typename R
        , ENABLE_IF(
               is_unique_ptr_propagate_cv<remove_cvref_t<L>>::value
            || is_unique_ptr_propagate_cv<remove_cvref_t<R>>::value
        )
    >
    bool operator<=(L&& lhs, R&& rhs) noexcept
    {
        return detail::get_underlying_unique_ptr(std::forward<L>(lhs))
            <= detail::get_underlying_unique_ptr(std::forward<R>(rhs));
    }
    
    template <typename L, typename R
        , ENABLE_IF(
               is_unique_ptr_propagate_cv<remove_cvref_t<L>>::value
            || is_unique_ptr_propagate_cv<remove_cvref_t<R>>::value
        )
    >
    bool operator>=(L&& lhs, R&& rhs) noexcept
    {
        return detail::get_underlying_unique_ptr(std::forward<L>(lhs))
            >= detail::get_underlying_unique_ptr(std::forward<R>(rhs));
    }
    
    template <typename L, typename R
        , ENABLE_IF(
               is_unique_ptr_propagate_cv<remove_cvref_t<L>>::value
            || is_unique_ptr_propagate_cv<remove_cvref_t<R>>::value
        )
    >
    bool operator<(L&& lhs, R&& rhs) noexcept
    {
        return detail::get_underlying_unique_ptr(std::forward<L>(lhs))
             < detail::get_underlying_unique_ptr(std::forward<R>(rhs));
    }
    
    template <typename L, typename R
        , ENABLE_IF(
               is_unique_ptr_propagate_cv<remove_cvref_t<L>>::value
            || is_unique_ptr_propagate_cv<remove_cvref_t<R>>::value
        )
    >
    bool operator >(L&& lhs, R&& rhs) noexcept
    {
        return detail::get_underlying_unique_ptr(std::forward<L>(lhs))
             > detail::get_underlying_unique_ptr(std::forward<R>(rhs));
    }
    

    Thanks for your help and reminding me that it was just a propagation issue.