为什么指针向量不能转换为const指针的const向量?

时间:2013-10-01 18:00:31

标签: c++ casting const

vector<char *>类型无法转换为const vector<const char*>。例如,以下内容给出了编译错误:

#include <vector>

using namespace std;

void fn(const vector<const char*> cvcc)
{
}

int main()
{
    vector<char *> vc = vector<char *>(); 

    fn(vc);
}

我理解为什么vector<char*>无法转换为vector<const char*> - 类型const char *的额外成员可能会被添加到向量中,之后它们可以作为非const访问。但是,如果向量本身是常量,则不会发生这种情况。

我最好的猜测是,这将是无害的,但是编译器无法推断出这将是无害的。

如何解决这个问题?

这个问题是由C ++ FQA here提出的。

4 个答案:

答案 0 :(得分:11)

通常,C ++不允许您将someclass<T>投射到someclass<U>,因为模板someclass可能专门用于UTU之间的关系并不重要。这意味着实现以及对象布局可能不同。

可悲的是,没有办法告诉编译器在哪些情况下布局和/或行为没有改变并且应该接受演员表。我想这对std::shared_ptr和其他用例非常有用,但这不会很快发生(AFAIK)。

答案 1 :(得分:6)

void fn(const vector<const char*>)

当函数类型的顶级const限定符被删除时,这是(在调用站点)相当于:

void fn(vector<const char*>)

两者都请求传递的向量的副本,因为标准库容器遵循值语义。

你可以:

  • 通过fn({vc.begin(), vc.end()})调用它,请求显式转换
  • 将签名更改为,例如void fn(vector<const char*> const&),即参考

如果您可以修改fn的签名,则可以关注GManNickGadvice and use iterators / a范围:

#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));
}

这给出了漂亮的输出

hello world
ello world
llo world
lo world
o world
 world
world
orld
rld
ld
d

根本问题是传递标准库容器。如果您只需要持续访问数据,则无需知道实际的容器类型,而是可以使用模板。这将删除fn与调用者使用的容器类型的耦合。

正如您所注意到的,允许通过std::vector<T*>访问std::vector<const T*>&是一个坏主意。但是,如果您不需要修改容器,则可以改为使用范围。

如果函数fn不能或不能作为模板,您仍然可以传递范围const char*而不是const char的向量。这适用于任何保证连续存储的容器,例如原始数组,std::arraystd::vectorstd::string

答案 2 :(得分:1)

要解决此问题,您可以实现自己的模板包装器:

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());
    }
    //...
};

我们的想法是包装器提供了引用向量的接口,但返回值始终为const T *。由于引用的向量是const,因此包装器提供的所有接口也应该是const,如[]运算符所示。这包括由包装器提供的迭代器。包装器迭代器只包含引用向量的迭代器,但产生指针值的所有操作都是const T *

此变通方法的目标不是提供可以传递给想要const std::vector<const T *> &的函数的东西,而是提供用于提供此类向量的类型安全性的函数的不同类型。 / p>

虽然您无法将此传递给期望const std::vector<const T *> &的函数,但您可以实现一个转换运算符,该运算符将返回使用基础向量中的指针值初始化的std::vector<const T *>的副本。上面的例子也说明了这一点。

答案 3 :(得分:-3)

正如FQA所暗示的,这是C ++的一个根本缺陷。

看来你可以通过一些明确的演员来做你想做的事情:

vector<char*> vc = vector<char *>(); 
vector<const char*>* vcc = reinterpret_cast<vector<const char*>*>(&vc);
fn(*vcc);

这会调用未定义的行为,但不保证可以正常工作;但是,我几乎可以肯定它会在关闭严格别名(-fno-strict-aliasing)的gcc中工作。无论如何,这只能作为临时黑客行事;你应该只是复制矢量,以保证的方式做你想做的事。

std::copy(vc.begin(), vc.end(), std::back_inserter(vcc));

从性能角度来看,这也没关系,因为fn在调用时会复制其参数。