-1

the question is pretty simple, is it in general safe a static cast (or some other cast) from

std::vector< Foo >

to

std::vector< const Foo >

binary-wise, i don't see why the native types would differ, after all, the const is a language constraint that should not affect the size of the element, or so i think

can i do

std::vector< const Foo >& someFunc()
{
  std::vector< Foo >& ref = ...
  return *reinterpret_cast<std::vector< const Foo >*>(& ref);
}

and not worry that this will sink someone's boat? or is this unsafe in general?

lurscher
  • 25,930
  • 29
  • 122
  • 185
  • Possible dupe? http://stackoverflow.com/questions/12077259/const-cast-stdvector – Abe Schneider Jan 09 '13 at 03:51
  • hope that edit makes it clearer – lurscher Jan 09 '13 at 03:53
  • 1
    not a dupe of that question - the const the OP in that question is trying to remove is to the vector not the element types, and also, i don't want to relax the const qualification, i want to restrict it (by returning a vector of const from a vector of non-const) – lurscher Jan 09 '13 at 03:56
  • 3
    @lurscher: Before thinking about whether it is unsafe, try to compile your example code. – Jesse Good Jan 09 '13 at 04:04
  • i mentioned static_cast but is not specific to it, i just need a safe way to cast one to the other, or a understandable reason why i can't do this safely (maybe reinterpret_cast will work?) – lurscher Jan 09 '13 at 04:09
  • @lurscher Ok, that's clearer now. I agree, binary-wise, I don't see how it could be an issue. The fact that you're going from non-const to const, means you are going in the safe direction. However, as Jesse points out, I don't think the compiler will like that code. You might have to use pointers. – Abe Schneider Jan 09 '13 at 04:15
  • I'm trying to understand the *purpose* behind doing this in the first place. Are you wanting this to return a modifiable vector of unmodifiable objects (apart from the obvious destruction of them when the vector is cleared on destruction) ? – WhozCraig Jan 09 '13 at 04:15
  • 1
    @WhozCraig i left out the const of std-vector in the hope of making the point clearer - yes, an actually clean interface should return a const reference to any private member storage. The point here is, that the member storage is a non-const vector of non-const stuff, and i want to return a const-vector of const-stuff from the interface, avoiding the overhead of a copy – lurscher Jan 09 '13 at 04:17
  • @AbeSchneider, it seems safe, but who knows what they feed to these compilers nowadays.. – lurscher Jan 09 '13 at 04:19
  • 1
    I'm trying to see how there *won't* be a copy regardless. There is no reference on the return type of `someFunc()`, which is where I'm confused by your last statement. If you don' want a copy, return a `const std::vector&`. All the non-const members and operators on the returned reference will cripple all but read-only access to the content, and no copying. (assuming your returned reference is to something living beyond the lifetime of `someFunc()` – WhozCraig Jan 09 '13 at 04:22
  • you are taking it too literal, but i edited it to appease these concerns – lurscher Jan 09 '13 at 04:23

1 Answers1

2

Ignoring that you said std::vector for the moment and pretending you had some other less well defined vector implementation. Your code would be technically unsafe not because T and T const are quite different but because the C++ language permits vector<T> and vector<T const> to be specialised in ways that are quite different. Consider the following code:

#include <iostream>

template <class T>
struct vector {
    T* start_;
    T* end_;
    T* cap_end_;
};

template <class T>
struct vector<T const> {
    bool gotcha_;
    T* start_;
    T* end_;
    T* cap_end_;
};

struct foo { };

int
main()
{
    std::cout
        << sizeof(vector<foo>) << '\n'
        << sizeof(vector<foo const>) << '\n'
        ;
}

Note that there are other more pernicious changes that could make your life miserable. Such as the following where the members are reordered:

#include <iostream>

template <class T>
struct vector {
    T* start_;
    T* end_;
    T* cap_end_;
};

template <class T>
struct vector<T const> {
    T* end_;
    T* cap_end_;
    T* start_;
};

template <class T>
long size(vector<T> const& v)
{
    return v.end_ - v.start_;
}

struct foo { };

int
main()
{
    vector<foo> v;
    v.start_ = new foo[10];
    v.end_ = v.start_ + 1;
    v.cap_end_ = v.start_ + 10;


    std::cout
        << size(v) << '\n'
        << size(*reinterpret_cast<vector<foo const>*>(&v)) << '\n'
        ;

    return 0;
}

Wrt to std::vector, I am not familiar enough with the fine details of the standard library specification to know whether such specialisations would be conformant or not. Perhaps someone more well versed in the standard can comment.

Note some of what I said in answer to Casting templated class to more general specialization may help explain this problem.

To address your question about detecting specialisations there are ways to make your code unsafe by using no specialisations of the class but overloaded non-member functions and I am o not sure how you would detect that. Such as in the following:

#include <iostream>

template <class T>
struct vector {
    T* start_;
    T* end_;
    T* cap_end_;
};


template <class T>
void init(vector<T>& v, size_t sz, size_t cap)
{
    v.start_ = new T[cap];
    v.end_ = v.start_ + sz;
    v.cap_end_ = v.start_ + cap;
}

template <class T>
void init(vector<T const>& v, size_t sz, size_t cap)
{
    v.end_ = new T const[cap];
    v.cap_end_ = v.end_ + sz;
    v.start_ = v.end_ + cap;
}

template <class T>
long size(vector<T>& v)
{
    return v.end_ - v.start_;
}

template <class T>
long size(vector<T const>& v)
{
    return v.cap_end_ - v.end_;
}

struct foo { };

int
main()
{
    vector<foo const> v;
    init(v, 1, 10);

    std::cout
        << size(v) << '\n'
        << size(*reinterpret_cast<vector<foo>*>(&v)) << '\n'
        ;
}

Enough with the bad news. The good news is that if you want to take an existing object with a general interface and restrict or adjust what can be done with that object there is are some simple, safe and comprehensible ways of doing that. Take a look at std::stack http://www.sgi.com/tech/stl/stack.html or alternatively this answer https://stackoverflow.com/a/994925/453436 to What is Proxy Class in C++

Community
  • 1
  • 1
Bowie Owens
  • 2,798
  • 23
  • 20
  • ah good point, so the lingering question is if std containers attempt to keep this sort of cast safe – lurscher Jan 09 '13 at 05:05
  • is possible that there is some template or static assert check that one can introduce to validate that this cast is safe at compile-time? maybe some way to check that a two template instantiations do not use different specialisations? – lurscher Jan 09 '13 at 18:25
  • @lurscher I have updated my answer to reflect your question about detecting specialisations. I don't think it is possible. I do, however, suspect that what you want to do can be done with an adaptor or a proxy object relatively easily. Although maybe I am misunderstanding what you are ultimately trying to achieve. – Bowie Owens Jan 10 '13 at 01:26
  • the ultimate goal is that if i hold a private member of type `std::vector< T* >`, i would like to provide an interface that returns a `const std::vector< const T* >&` that users can read but not write to it, and avoid creating a vector copy altogether, for what it is essentially a read-only operation – lurscher Jan 10 '13 at 06:47
  • 1
    @lurscher A proxy object will let you do exactly that. You just have to write a custom object that holds a reference to the private member and has an interface that includes only the permitted operations that are implemented by delegating to the reference. – Bowie Owens Jan 10 '13 at 22:38