Cast of std::vector of same parameter type but wit

2019-03-06 17:58发布

问题:

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?

回答1:

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++