Reputation: 22728
The relevant interface code that I have to deal with consists of two functions, one that retrieves objects, and the other one where I have to submit the objects as vectors. The problem is, that the retrieval function returns const Object*
, but the submit function expects const vector<Object*>
.
I know this is solveable with const_cast<Object*>
, but is there a different, cleaner way?
Here is code that demonstrates the problem:
#include <vector>
//////////// REPRESENTATIVE INTERFACE IMPLEMENTATION, DO NOT TOUCH ///////
struct Object{};
class Interface{
public:
size_t getNumObjects() const {return 10;}
const Object* getObject(size_t index) const {return nullptr;}
};
const Interface interface;
void submitObjects(const std::vector<Object*> &objects);
//////////////////////////////////////////////////////////////////////
// Task: take all objects from 'interface' and submit them to 'submitObjects'.
int main(){
std::vector<const Object*> objects;
for(size_t i = 0; i < interface.getNumObjects(); i++){
const Object* object = interface.getObject(i);
objects.push_back(object);
}
submitObjects(objects); // ERROR: no known conversion from 'vector<const Object *>' to 'const vector<Object *>'
return 0;
}
The only way that I could come up with to solve that problem is to make objects
a std::vector<Object*>
and insert the objects with objects.push_back(const_cast<Object*>(object));
, but I feel like there has to be a better solution.
Any ideas are appreciated.
Some more background info why this is a valid problem:
const vector<Object*>
can only deliver const Object*
, as seen in all of its getter overloads, like seen here: https://de.cppreference.com/w/cpp/container/vector/operator_at
Therefore, vector<const Object*>
is almost never used in interfaces. Therefore, the idea of combining a lot of const Object*
in one big const vector<Object*>
is a valid concept, but seems impossible to construct without const_cast
.
EDIT: Statement above is incorrect, thanks for clarifying.
const std::vector<Object*>
can produce non-const pointers, because my understanding of pointer constness was incorrect.
Upvotes: 3
Views: 703
Reputation: 238401
Your options are:
submitObjects
to accept std::vector<const Object*>
.Interface::getObject
to return pointer to non-const Object
.const_cast
, as you figured out. Avoid this if you can.If submitObjects
modifies the pointed objects, then 3. is dangerous if the pointed objects shouldn't be modified (such as if they are actually const objects). If it is OK for the objects to be modified, then choose 2.
If submitObjects
does not modify the pointed objects, then there should be no problem with doing the change 1.
If submitObjects
modifies the pointed objects, and it is not OK for the objects to be modified, then you have a logical contradiction, and there is no solution other than to not pass the pointers to the function.
it is impossible to retrieve mutable pointers from a
const vector<Object*>
?
It is only the pointers that you may not mutate. But you may mutate the pointed objects. Example:
struct Object{ int member; };
Object o;
const vector<Object*> objects{&o};
objects[0]->member = 42; // well-defined
I was thinking about 1., but that would mean that it would no longer accept std::vector, requiring yet another overload
Or, you could use a template. In particular, this may potentially be a good use case for input iterators, or ranges.
Upvotes: 4
Reputation: 41780
Your interface returns pointer to constant object. You want to send those constant object into a function that want mutable objects.
It's not just about undefined behaviour, it's about surprises.
Imagine the one implementing the interface is keeping calculated values about the objects and is flagging dirty when calling a potentially mutating method. There would a compeling reason to indeed return pointer to constant objects. Modifying the const object would break a class invariant.
If you send that constant to submitObjects
and your object get mutated there, then you lied in a sense: an interface send you constant objects and you mutated them. You broke the contract.
If submitObjects
don't mutate its arguments, then there is no reason to keep the argument mutable.
Reguarding the confusion about const, here's an example of a const vector of pointer to mutable data:
// const std::vector<int*>
auto const cannot_erase_or_add = std::vector<int*>{
new int{1},
new int{2},
new int{3}
};
// Error!
// cannot_erase_or_add.push_back(new int{4});
// Error!
// cannot_erase_or_add.clear();
// operator[] return int*, because it's a vector of int*
int* one = cannot_erase_or_add[0];
// totally legal, int*, no const there
*one = 2;
On the other hand, here's the contrary:
// std::vector<int const*>
auto can_erase_or_add = std::vector<int const*>{
new int{1},
new int{2},
new int{3}
};
// Works
can_erase_or_add.clear();
// Works
can_erase_or_add.push_back(new int{4});
// operator[] return int const*, because it's a vector of int const*
int const* four = can_erase_or_add[0];
// Can't do that, pointer to const
// *four = 2;
Upvotes: 5
Reputation: 23701
You misunderstand the documentation/typing rules. If you have T = Object*
, then const T
is not const Object*
but Object* const
. This how C and C++ work.
Therefore, your assertion that "you cannot get a mutable object from const vector<Object*>
is wrong. What you cannot do is modify the vector or the values of the elements (i.e. change the pointers). But the accessors return references to Object* const
, so can still modify the pointed-to objects:
void foo(const std::vector<int*>& v)
{
(*v[0])++; // Increments the integer pointed to by the first vector element.
}
This is simply due to const
not being transitive. Here is another example:
struct X
{
int* y;
};
void foo(const X& x)
{
(*x.y)++;
}
Given this, the interface you have does not allow what you want to do. You are only handed pointers to immutable objects but are supposed to pass on pointers to mutable objects (the submitObjects
function only promises not to modify the container, but it reserves the right to modify the objects). This is simply impossible.
const_cast
could be a valid option if (and that's a very important if!) you know 100% that the objects are not actually declared as const
wherever they are created. But this both defeats the point of the interface and is an extremely fragile, hacky approach that you should not even consider in this circumstance.
Upvotes: 7