Const casting container value-types seems not possible. A comment in the other question suggests iterators as a solution, yet does not go into detail. Since I seemingly cannot simply convert a container from a non-const to a const version as a function parameter, I arrive at Iterators to maybe be able to do the job.
I actually have a vector<shared_ptr<Thing> >
to be treated as const vector<shared_ptr<Thing const> >
.
With it I intend to use the shared_ptr<Thing const>
as further references in other structures, without allowing those structures to alter the Thing
s. Those structures may create their own objects, stored by their own shared_ptr, if they want slightly different content within their containers, while still actively sharing most Things
with other objects.
So I would need either shared_ptr<const Thing>&
, or const shared_ptr<const Thing>&
from an Iterator through the sequence. Either would suffice, but just because one can be indifferent about passing references in this example, because of shared_ptr's copy semantics are about just that.
Yet even just using default const_iterator
, retrieved by cbegin()
,c.end()
and such, will give me a const shared_ptr<Thing>&
instead.
Edit: To copy the vector element for element would be one way technically, as in the other question, yet undesired for interface reasons. I am going for reinterpretation here, not copy.
Any suggestions on where a workaround might lie?
Based on your situation, it sounds like defining a custom iterator with the semantics you want is the safe and simple way to go. It's technically correct, hard to accidentally misuse, and fairly fast, just requiring a shared_ptr
copy on iterator dereference.
I always recommend boost::iterator_facade
or boost::iterator_adaptor
for creating an iterator type. Since it will contain the original vector
iterator as a "base" implementation, iterator_adaptor
is helpful.
class const_Thing_ptr_iterator :
public boost::iterator_adaptor<
const_Thing_ptr_iterator, // CRTP derived type
std::vector<std::shared_ptr<Thing>>::const_iterator, // base iterator type
std::shared_ptr<const Thing>, // value_type
std::random_access_iterator_tag, // traversal type
std::shared_ptr<const Thing> // reference
>
{
public:
const_Thing_ptr_iterator() = default;
explicit const_Thing_ptr_iterator(base_type iter)
: iterator_adaptor(iter) {}
};
The reference
iterator type would be std::shared_ptr<const Thing>&
by default, but in this case it can't be a reference since there is no object of that type. Normally the class would define some of the behavior functions like dereference
or increment
, but none are needed here: the only change from the base vector iterator is to the return type of operator*
, and the default reference dereference() const { return *base_reference(); }
works fine by implicit conversion from const std::shared_ptr<Thing>&
to std::shared_ptr<const Thing>
.
The class could also be a template taking Thing
as its type parameter, to create multiple iterator types.
Then to provide a container-like view object, we can use C++20's std::ranges::subrange
to provide begin()
and end()
and a few other things helping the out the range templates:
#include <ranges>
class const_Thing_ptrs_view
: public std::ranges::subrange<const_Thing_ptr_iterator>
{
public:
explicit const_Thing_ptrs_view(const std::vector<std::shared_ptr<Thing>> &vec)
: subrange(const_Thing_ptr_iterator(vec.begin()),
const_Thing_ptr_iterator(vec.end())) {}
};
Or if that's not available, a simple class with begin()
and end()
:
class const_Thing_ptrs_view {
public:
explicit const_Thing_ptrs_view(const std::vector<std::shared_ptr<Thing>> &vec)
: m_begin(vec.begin()), m_end(vec.end()) {}
const_Thing_ptr_iterator begin() const { return m_begin; }
const_Thing_ptr_iterator end() const { return m_end; }
private:
const_Thing_ptr_iterator m_begin;
const_Thing_ptr_iterator m_end;
};
Demo on godbolt. (Clang doesn't like the ranges code due to this libstdc++ incompatibility; I'm not sure how to get godbolt to switch it to clang's libc++.)