Let's say I have a Storage
of some Object
s which has a method that aggregates pointers to some of the Objects
in a vector. Like this:
class Storage
{
public:
std::vector<Object*> aggregate_some_objects(); // non-const version
std::vector<const Object*> aggregate_some_objects() const; // const version
private:
std::unordered_map<size_t, Object> m_objects; // data is stored
// by-value in a non-vector container
}
Generally, there is way to avoid copy-paste in implementation of const + non-const method pairs by calling one of them inside the other with the help of const_cast
. Here however this is not possible because the return types of the methods are different.
The most straightforward way to avoid copy-paste here would be to call const
version from a non-const
version and use the returned std::vector<const T*>
to populate a separate std::vector<T*>
. However this would lead to at least 2 heap allocation (one for each vector). I would like to avoid the allocations associated with the second vector.
I wonder if there is way to write something like
template <typename T>
std::vector<T*> remove_const_from_vector_of_ptrs(std::vector<const T*>&& input)
{
std::vector<T*> ret;
// do some magic stuff here that does not involve
// more memory allocations
return ret;
}
Thus, allowing to write
std::vector<const Object*> Storage::aggregate_some_objects() const
{
// non-trivial implementation
}
std::vector<Object*> Storage::aggregate_some_objects()
{
auto objects = const_cast<const Storage*>(this)->aggregate_some_objects();
return remove_const_from_vector_of_ptrs(std::move(objects));
}
There is no 'release' method in std::vector
(like std::unique_ptr
for example) that allows transferring of memory ownership - and for a very good reason, so I expect that this is not possible.
I also understand that if it were possible, it would be a dangerous operation that should be generally avoided, just as const_cast
. But a careful usage in cases like this seems more beneficial than copy-pasting.
Edit: added clarifications to what do I mean by 'extra' allocations and changed Storage::aggregate_objects()
to Storage::aggregate_some_objects()
to better indicate that the implementation of these methods is more complex then a range-based loop - hence the desire to avoid copy-pasting the implementation.
The short answer is: no. std::vector<Object*>
and std::vector<const Object*>
are two different, independent classes. They are as different from each other as class A
is from class B
. It is often thought that just because both of them start with std::vector
, that they are somehow related to each other. This is not true, and for that reason there is no way to convert one to the other, "in place". Each one of these vector classes own their corresponding internal data()
, and will not willingly give it up to some other, strange class.
The long answer is still no, but in many cases it's possible to work around this in order to avoid manual code duplication. The truth of it is that code duplication is inevitable in most of these kinds of situations, and the best that can be done is to avoid manual code duplication.
One common approach is have both the constant and the mutable class method be facades for a single, shared, private template:
// Header file:
class Storage {
public:
std::vector<const Object*> aggregate_objects() const;
std::vector<Object*> aggregate_objects();
private:
template<typename v_type> void make_aggregate_objects(v_type &v) const;
};
// In the translation unit:
template<typename v_type> void Storage::make_aggregate_objects(v_type &v) const
{
// Now, create 'v' here... v.reserve(), v.push_back(), etc...
}
std::vector<const Object*> Storage::aggregate_objects() const
{
std::vector<const Object *> v;
make_aggregate_objects(v);
return v;
}
std::vector<Object*> Storage::aggregate_objects()
{
std::vector<const Object *> v;
make_aggregate_objects(v);
return v;
}
The compiler will still generate two nearly identical chunks of code, but at least it's not you doing all the typing.
Another, similar approach, is to pass a lambda to the template function instead of passing a vector object, with the private template function using the lambda function as a callback, to construct the returned vector. With a bit of type erasure, and some help from std::function
, the private class method can be turned into an ordinary method, instead of a template method.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With