21

该类型 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

4 回答 4

13

通常,C++ 不允许您转换someclass<T>为模板,someclass<U>因为模板someclass可能专门用于U. 无关紧要,如何T相关U。这意味着实现和对象布局可能不同。

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

于 2013-10-01T18:07:01.243 回答
7
void fn(const vector<const char*>)

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

void fn(vector<const char*>)

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

您可以:

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

如果您可以修改 的签名fn,则可以遵循GManNickG建议并使用迭代器/ 范围代替:

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

这给出了漂亮的输出

你好世界
你好世界
世界
世界
世界
 世界
世界
世界
rld
ld
d

基本问题是传递标准库容器。如果您只需要不断地访问数据,则不需要知道实际的容器类型,而可以使用模板来代替。这消除了fn与调用者使用的容器类型的耦合。

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

如果该函数fn不应或不能是模板,您仍然可以传递范围const char*而不是向量const char。这将适用于任何保证连续存储的容器,例如原始数组、std::arrays、std::vectors 和std::strings。

于 2013-10-01T23:56:15.803 回答
2

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

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,如[]操作员所说明的那样。这包括将由包装器提供的迭代器。包装迭代器将只包含一个指向引用向量的迭代器,但所有产生指针值的操作都是 a const T *

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

void fn(const_ptr_vector<char *> cvcc)
{
}

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

于 2013-10-01T18:49:11.970 回答
-1

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

看来您可以通过一些显式转换来做您想做的事情:

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

这会调用未定义的行为并且不能保证有效;但是,我几乎可以肯定它会在关闭严格别名的 gcc 中工作 ( -fno-strict-aliasing)。无论如何,这只能作为临时破解;您应该只复制向量以有保证的方式执行您想要的操作。

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

从性能的角度来看,这也是可以的,因为fn在调用它时会复制它的参数。

于 2013-10-01T18:26:03.953 回答