Why is a vector of pointers not castable to a const vector of const pointers?

后端 未结 4 2044
梦谈多话
梦谈多话 2021-01-03 22:17

The type vector is not convertible to const vector. For example, the following gives a compilation error:

相关标签:
4条回答
  • 2021-01-03 22:44

    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.

    0 讨论(0)
  • 2021-01-03 22:57
    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:

    • call it via fn({vc.begin(), vc.end()}), requesting an explicit conversion
    • change the signature to, e.g. void fn(vector<const char*> const&), i.e. taking a reference

    If 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::arrays, std::vectors and std::strings.

    0 讨论(0)
  • 2021-01-03 22:58

    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).

    0 讨论(0)
  • 2021-01-03 23:04

    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.

    0 讨论(0)
提交回复
热议问题