vector and const

28,566

Solution 1

I've added a few lines to your code. That's sufficient to make it clear why this is disallowed:

void f(vector<const T*>& p)
 {
    static const T ct;
    p.push_back(&ct); // adds a const T* to nonConstVec !
 }
 int main()
 { 
  vector<T*> nonConstVec;
  f(nonConstVec);
  nonConstVec.back()->nonConstFunction();
 }

Solution 2

vector<T> and vector<const T> are unrelated types. The fact that T can be converted to const T doesn't mean a thing here.

You have to think about it from a type system's standpoint. Instantiated vector<int> doesn't have anything in common with vector<const int>.

Solution 3

It may be worth showing why it's a breach of const-correctness to perform the conversion you want:

#include <vector>
const int a = 1;

void addConst(std::vector<const int *> &v) {
    v.push_back(&a); // this is OK, adding a const int* to a vector of same
}

int main() {
    std::vector<int *> w;
    int b = 2;
    w.push_back(&b);  // this is OK, adding an int* to a vector of same
    *(w.back()) = 3;  // this is OK, assigning through an int*
    addConst(w);      // you want this to be OK, but it isn't...
    *(w.back()) = 3;  // ...because it would make this const-unsafe.
}

The problem is that vector<int*>.push_back takes a pointer-to-non-const (which I'll call a "non-const pointer" from now on). That means, it might modify the pointee of its parameter. Specifically in the case of vector, it might hand the pointer back out to someone else who modifies it. So you can't pass a const pointer to the push_back function of w, and the conversion you want is unsafe even if the template system supported it (which it doesn't). The purpose of const-safety is to stop you passing a const pointer to a function which takes a non-const pointer, and this is how it does its job. C++ requires you to specifically say if you want to do something unsafe, so the conversion certainly can't be implicit. In fact, because of how templates work, it's not possible at all (see later).

I think C++ could in principle preserve const-safety by allowing a conversion from vector<T*>& to const vector<const T*>&, just as int ** to const int *const * is safe. But that's because of the way vector is defined: it wouldn't necessarily be const-safe for other templates.

Likewise, it could in theory allow an explicit conversion. And in fact, it does allow an explicit conversion, but only for objects, not references ;-)

std::vector<const int*> x(w.begin(), w.end()); // conversion

The reason it can't do it for references is because the template system can't support it. Another example that would be broken if the conversion were allowed:

template<typename T> 
struct Foo {
    void Bar(T &);
};

template<>
struct Foo<const int *> {
    void Baz(int *);
};

Now, Foo<int*> doesn't have a Baz function. How on earth could a pointer or reference to Foo<int*> be converted to a pointer or reference to Foo<const int*>?

Foo<int *> f;
Foo<const int *> &g = f; // Not allowed, but suppose it was
int a;
g.Baz(&a); // Um. What happens? Calls Baz on the object f?

Solution 4

Think of like this:

You have two class like this:

class V  { T*       t;};
class VC { T const* t;};

Do you expect these two classes to be convertible automatically?
This is basically what a template class is. Each variation is a completely new type.

Thus vector<T*> and vector<T const*> are completely different types.

My first question is do you really want to store pointers?

If yes, I would suggest looking at boost::ptr_container. This holds pointers and deletes them when the vector is destroyed. But more importantly it treats the contained pointers as a normal std:vector treats its contained objects. Thus by making the vector const you can only access its members as const

void function(boost::ptr_vector<T> const& x)
{
     x.push_back(new T);  // Fail x is const.
     x[4].plop();         // Will only work if plop() is a const member method.
}

If you don't need to store pointers then store the objects (not the pointers) in the container.

void function(std::vector<T> const& x)
{
     x.push_back(T());    // Fail x is const.
     x[4].plop();         // Will only work if plop() is a const member method.
}

Solution 5

Others have already given the reason why the code you gave doesn't compile, but I have a different answer on how to deal with it. I don't believe there's any way to teach the compiler how to automatically convert the two (because that would involve changing the definition of std::vector). The only way around this annoyance is to do an explicit conversion.

Converting to a completely different vector is unsatisfying (wastes memory and cycles for something that should be completely identical). I suggest the following:

#include <vector>
#include <iostream>

using namespace std;

typedef int T;

T a = 1;
T b = 2;

void f(vector<const T*>& p)
{
    for (vector<const T*>::const_iterator iter = p.begin(); iter != p.end(); ++iter) {
        cout << **iter << endl;
    }
}
vector<const T*>& constify(vector<T*>& v)
{
  // Compiler doesn't know how to automatically convert
  // std::vector<T*> to std::vector<T const*> because the way
  // the template system works means that in theory the two may
  // be specialised differently.  This is an explicit conversion.
  return reinterpret_cast<vector<const T*>&>(v);
}
int main()
{
  vector<T*> nonConstVec;
  nonConstVec.push_back(&a);
  nonConstVec.push_back(&b);
  f(constify(nonConstVec));
}

I'm using reinterpret_cast to declare that the two things are the same. You SHOULD feel dirty after using it, but if you put it in a function by itself with a comment for those following you, then have a wash and try to continue on your way with a good conscience, though you will always (rightly) have that nagging worry about someone pulling the ground out from under you.

Share:
28,566

Related videos on Youtube

user152508
Author by

user152508

Updated on July 09, 2022

Comments

  • user152508
    user152508 almost 2 years

    Consider this

     void f(vector<const T*>& p)
     {
     }
     int main()
     { 
      vector<T*> nonConstVec;
      f(nonConstVec);
     }
    

    The following does not compile.The thing is that vector<T*> can not be converted to vector <const T*> , and that seems illogically to me , because there exists implicit conversion from T* to const T*. Why is this ?

    vector<const T*> can not be converted to vector <T*> too, but that is expected because const T* can not be converted implicitly to T*.

  • user152508
    user152508 over 14 years
    Than how to pass my vector<T*> to the function that gets vector<const T*>? I certainly do not want to change the signature of the function because it is like comment that says that it will not modify the passed object.
  • Admin
    Admin over 14 years
    Well, you will have to change it, I'm afraid. If you want to say you are not changing the vector, then you want f( const vector <T*> & );
  • user152508
    user152508 over 14 years
    f(const <T*>&) means that the vector (contains pointers) itself will not be changed, but the objects the pointers point to can be changed. And that is not I want.
  • GManNickG
    GManNickG over 14 years
    This is benefit of splitting the algorithm from it's underlying type. @John's answer demonstrates this.
  • UncleBens
    UncleBens over 14 years
    I think you just need to "suck it up". As others have shown, allowing your way would break const-correctness elsewhere. You can minimize risks in other ways, e.g if you are going to use each pointer in turn, make that a separate function that accepts a const pointer.
  • Alexandre C.
    Alexandre C. over 13 years
    great, I never thought about this
  • Thaqif Yusoff
    Thaqif Yusoff about 11 years
    if this was the reason, converting to const vector<const T*> should work
  • MSalters
    MSalters about 11 years
    @marcin: That's theoretically safe, but there is no way for the compiler to determine that without detailed knowledge of std::vector. Also, to make it legal would require a complex change to the conversion sequence rules. What are the requirements on template <class T> class Foo for a Foo<T*> to be casted to a const Foo<const T*> ? Remember, Foo in this context could be a smart pointer template or anything else, not necessarily a container.
  • Praxeolitic
    Praxeolitic about 10 years
    Great answer. This clarified for me exactly what it is that's illegal and why, from the compiler's perspective, it is not allowed. The other answers explain why it might be a bad idea, which is good to know, but lots of legal code is a bad idea.
  • bolov
    bolov over 8 years
    A lot of explanations on the web just point to the fact that vector<T *> and vector<T const *> are unrelated types. That doesn't explain why a conversion isn't provided (after all this is C++ and we have converting constructors and the overload-able conversion operator). This is the simplest and most clear example why adding a conversion would be a bad thing.
  • Llopeth
    Llopeth over 6 years
    I agree it is worth reading. Despite its aggresive style, the FQA is a good way to consolidate your c++ knowledge