如何 const_cast 一个常量指针向量指向一个非常量指针向量?

How to const_cast a vector of const pointers to a vector of non-const pointers?

我有一个函数正在自定义列表实现中查找元素。为了使其成为 const 正确的,我可以进行这种巧妙的重载,甚至可以使用单行代码来重用完全相同的实现,因此不必重复逻辑。

const MyObject* findObject(const MyList& list)
{
  //...
}

MyObject* findObject(MyList& list)
{
  return const_cast<MyObject*>(findObject(const_cast<const MyList&>(list)));
}

问题是,一旦我需要 return 向量内的多个元素指针,禁止 unsave/non-portable 像 reinterpret_cast 这样的 hack,我该怎么办?

std::vector<const MyObject*> findObject(const MyList& list)
{
  //...
}

std::vector<MyObject*> findObject(MyList& list)
{
  // this is sth I'm looking for:
  const_element_cast<std::vector<MyObject*>>( findObject( const_cast<const MyList&>(list)) );
}

最简单的解决方案是忘记 const_cast 并显式实现两个重载。 这就是标准库实现所做的。

例如,std::vectorconstoperator[] 的非 const 版本。 VC++ 和 GCC 都对该运算符有重复的实现(分别参见 include/vectorstl_vector.h 文件);两者都没有诉诸任何 const_cast 技巧来复制行为。

现在,如果您的 findObject 实施非常庞大和复杂,那么首选应该是使其更简单。作为临时解决方法,您可以考虑根据内部(私有或其他不可访问的)模板函数实现两个重载,使用 decltype 尾随 return 类型来获得正确的 const 或非-const return 通过参数输入。这是一个简单的例子:

#include <iostream>
#include <vector>
#include <typeinfo> // just to demonstrate what's going on

// simple example data structure:
struct MyObject {};
struct MyList
{
    MyObject elements[3] = { MyObject {}, MyObject {}, MyObject {} };
};

namespace internal
{
    // &list.elements[0] will be either MyObject const* or MyObject*
    template <class ConstOrNonConstList>
    auto doFindObject(ConstOrNonConstList& list) -> std::vector<decltype(&list.elements[0])>
    {
        // let's say there was an immensely complicated function here,
        // with a lot of error potential and maintenance nightmares
        // lurking behind a simple copy & paste solution...

        // just to demonstrate what's going:
        std::cout << typeid(decltype(&list.elements[0])).name() << "\n";

        std::vector<decltype(&list.elements[0])> result;
        for (auto&& element : list.elements)
        {
            result.push_back(&element);
        }
        return result;
    }
}

std::vector<const MyObject*> findObject(const MyList& list)
{
    std::cout << "const version\n";
    return internal::doFindObject(list);
}

std::vector<MyObject*> findObject(MyList& list)
{
    std::cout << "non-const version\n";
    return internal::doFindObject(list);
}

int main()
{
    MyList list1;
    MyList const list2;
    auto obj1 = findObject(list1);
    auto obj2 = findObject(list2);
}

示例输出(取决于 typeid 在您的实施中产生的名称类型):

non-const version
struct MyObject *
const version
struct MyObject const *

但坦率地说,我不会这样做。它似乎设计过度,也许有点太聪明了。过于聪明的代码很少是个好主意,因为它会使人感到困惑。

选项 1

如果你愿意支付循环+复制价格(对比copy-pasting代码):

std::vector<const thing*> c_vec = static_cast<const me*>(this)->my_const_func();
std::vector<thing*> ret;

for (const thing* t : c_vec) {
    // Yes you can do this, since this func isn't const
    // so you are guaranteed to be inside non-const context.
    // See Scott Meyers.

    ret.push_back(const_cast<thing*>(t));
}
return ret;

选项 2

[edit] 如果您不想支付循环 + 复制价格,这是另一种选择。

假设您正在使用 cpp 文件(不是 header-only)。在你的编译单元 (.cpp) 中,添加一个模板化的 getter.

template <class T, class MyObj>
std::vector<T*> getter_imp(MyObj& obj, int someval) {
    assert(someval <= 42);

    std::vector<T*> ret;
    for (auto& i : obj._my_vec) {
        if (i < someval) {
            ret.push_back(&i);
        }
    }
    return ret;
}

为了保持整洁 "hidden",您可以在 class.

中将该功能添加为好友
template <class T, class MyObj>
friend std::vector<T*> getter_imp(MyObj&, int);

现在,您可以从 const 和 non-const 成员函数中调用 getter。没有 copy-paste(也就是我们生活的祸根)。

std::vector<const int*> get(int v) const {
    return getter_imp<const int>(*this, v);
}
std::vector<int*> get(int v) {
    return getter_imp<int>(*this, v);
}

这里发生了什么?

您在模板参数中编码 const。假设您从 const 函数调用 T = const intMyObj = const your_class。在函数内部,const 也被推导为 auto&

从非 const 上下文调用时,模板参数和 auto& 不是 const。所以它有效。