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.
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.