The type vector
is not convertible to const vector
. For example, the following gives a compilation error:
As the FQA suggests, this is a fundamental flaw in C++.
It appears that you can do what you want by some explicit casting:
vector<char*> vc = vector<char *>();
vector<const char*>* vcc = reinterpret_cast<vector<const char*>*>(&vc);
fn(*vcc);
This invokes Undefined Behavior and is not guaranteed to work; however, I am almost certain it will work in gcc with strict aliasing turned off (-fno-strict-aliasing
). In any case, this can only work as a temporary hack; you should just copy the vector to do what you want in a guaranteed manner.
std::copy(vc.begin(), vc.end(), std::back_inserter(vcc));
This is OK also from the performance perspective, because fn
copies its parameter when it's called.
void fn(const vector<const char*>)
As the top-level const
qualifier is dropped for the function type, this is (at the call site) equivalent to:
void fn(vector<const char*>)
Both of which request a copy of the passed vector, because Standard Library containers follow value semantics.
You can either:
fn({vc.begin(), vc.end()})
, requesting an explicit conversionvoid fn(vector<const char*> const&)
, i.e. taking a referenceIf you can modify the signature of fn
, you can follow GManNickG's advice and use iterators / a range instead:
#include <iostream>
template<typename ConstRaIt>
void fn(ConstRaIt begin, ConstRaIt end)
{
for(; begin != end; ++begin)
{
std::cout << *begin << std::endl;
}
}
#include <vector>
int main()
{
char arr[] = "hello world";
std::vector<char *> vc;
for(char& c : arr) vc.push_back(&c);
fn(begin(vc), end(vc));
}
This gives the beautiful output
hello world ello world llo world lo world o world world world orld rld ld d
The fundamental issue is to pass around Standard Library containers. If you only need constant access to the data, you don't need to know the actual container type and can use the template instead. This removes the coupling of fn
to the type of container the caller uses.
As you have noticed, it's a bad idea to allow access of a std::vector<T*>
through a std::vector<const T*>&
. But if you don't need to modify the container, you can use a range instead.
If the function fn
shall not or cannot be a template, you could still pass around ranges of const char*
instead of vectors of const char
. This will work with any container that guarantees contiguous storage, such as raw arrays, std::array
s, std::vector
s and std::string
s.
In general, C++ does not allow you to cast someclass<T>
to someclass<U>
as a template someclass
might be specialized for U
. It doesn't matter how T
and U
are related. This mean that the implementation and thus the object layout might be different.
Sadly, there is no way to tell the compiler in which cases the layout and/or behaviour didn't change and the cast should be accepted. I imagine it could be very useful for std::shared_ptr
and other use-cases, but that is not going to happen anytime soon (AFAIK).
To work around the issue, you could implement your own template wrapper:
template <typename T> class const_ptr_vector;
template <typename T> class const_ptr_vector<T *> {
const std::vector<T *> &v_;
public:
const_ptr_vector (const std::vector<T *> &v) : v_(v) {}
typedef const T * value_type;
//...
value_type operator [] (int i) const { return v_[i]; }
//...
operator std::vector<const T *> () const {
return std::vector<const T *>(v_.begin(), v_.end());
}
//...
};
The idea is that the wrapper provides an interface to the referenced vector, but the return values are always const T *
. Since the referenced vector is const
, all the interfaces provided by the wrapper should be const
as well, as illustrated by the []
operator. This includes the iterators that would be provided by the wrapper. The wrapper iterators would just contain an iterator to the referenced vector, but all operations that yield the pointer value would be a const T *
.
The goal of this workaround is not to provide something that can be passed to a function wanting a const std::vector<const T *> &
, but to provide a different type to use for the function that provides the type safety of such a vector.
While you could not pass this to a function expecting a const std::vector<const T *> &
, you could implement a conversion operator that would return a copy of a std::vector<const T *>
initialized with the pointer values from the underlying vector. This has also been illustrated in the above example.