简体   繁体   中英

Storing const* in const vector

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.

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;

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 (ie 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.
}

https://godbolt.org/z/HGIb20

This is simply due to const not being transitive. Here is another example:

struct X
{
    int* y;
};

void foo(const X& x)
{
    (*x.y)++;
}

https://godbolt.org/z/NmCwGt


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.

Your options are:

  1. Change submitObjects to accept std::vector<const Object*> .
  2. Or change Interface::getObject to return pointer to non-const Object .
  3. Or use 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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM