使用常量键但非常量值进行映射

map with const keys but non const values?

本文关键字:常量 映射 非常      更新时间:2023-10-16

我有一种情况,我想要一个不允许在初始化后添加/删除键的映射,但允许更改值(因此我不能简单地使映射const(。即

/*semi-const*/ map<int,int> myMap = initMap();
myMap[1] = 2;                  // NOT OK, because potentially adds a new key
myMap.at(1) = 2;               // OK, because works only if key is present
for (auto & element : myMap) {
    element.second = 0;        // OK, values may change
}

我可以为std::map编写自己的包装器,但我感觉这并不少见,所以我想知道是否已经有现有的解决方案。

对于不允许添加/删除键的地图,是否有一些标准习惯用法,而值可能会更改?

ps:我知道标题本身有点模糊,因为键已经在地图上了,但我希望它清楚我的意思......

您能否创建一个包含值的包装器,该值允许在const时发生突变,并将其放在map中?像这样:

template<typename T>
class Mutable {
    mutable T value;
public:
  const Mutable& operator=(const T& v) const { value = v; return *this; }
  T& get() const { return value; }  
};

然后您的地图可以是类型

const std::map<int, Mutable<int>>

现场演示。

我通常认为这是一个陷阱,C++不仅仅是一个功能,但是,如果它适合你的应用程序,你可以只使用指针值。

#include <map>
#include <memory>
int main(int argc, char ** argv)
{
    using namespace std;
    const map<int, shared_ptr<int>> myMap = { {1, make_shared<int>(100)} };
    // *(myMap[1]) = 2;  // Does not compile
    *(myMap.at(1)) = 2;
    for (auto & element : myMap)
    {
        *(element.second) = 0;
    }
    return 0;
}

这实际上只是另一个答案的更简单版本(显然您可以根据需要在shared_ptr/unique_ptr之间进行选择(。

标准库中的容器是针对一种用法优化的类,这些类应按原样使用或包含在更高级别的类中。

在这里,您的要求(初始化后修复的密钥(不在标准库容器中,因此您必须构建自己的实现。由于它不会std::map,因此您可以仅实现所需的操作,可能仅此而已operator []...

我知道您只是想禁用索引访问运算符,以便用户不会意外地将默认构造的项目添加到地图中。 我的解决方案受到克里斯·德鲁解决方案的启发,但具有保持常量正确的额外好处(即不允许在地图为常量时更改地图的值(。

实质上,通过禁用默认构造,您可以删除调用 std::map 提供的索引访问运算符的功能。 其他方法将保持可用,因为std::map是一个类模板,并且在调用成员函数之前不会计算它们。 因此,std::map::at可以正常工作,但std::map::operator[]会导致编译时错误。

受 Chris 的启发,您可以在mapped_type上使用包装器来禁用默认构造。 我拿了他的演示并对其进行了一些调整,以演示如何禁用默认构造并将其与std::map而不是const std::map一起使用。

template<typename T>
class RemoveDefaultConstruction {
   T value;
public:
   RemoveDefaultConstruction() = delete; // The magic is here
    RemoveDefaultConstruction(const RemoveDefaultConstruction &other) noexcept(std::is_nothrow_copy_constructible<T>::value) = default;
    RemoveDefaultConstruction(RemoveDefaultConstruction &&other) noexcept(std::is_nothrow_move_constructible<T>::value) = default;
    RemoveDefaultConstruction(T &&t) noexcept(std::is_nothrow_constructible<T, decltype(std::forward<T>(t))>::value) :
    value{std::forward<T>(t)} {
    }
    RemoveDefaultConstruction& operator=(const RemoveDefaultConstruction &other) = default;
    RemoveDefaultConstruction& operator=(RemoveDefaultConstruction &&other) = default;
    RemoveDefaultConstruction& operator=(T &&other) { value = std::move(other); return *this; }
    RemoveDefaultConstruction& operator=(T const &other) { value = other; return *this; }
    T const &get() const { return value; } // Keep const correctness
    T &get() { return value; } // Keep const correctness
};
void update(std::map<int, RemoveDefaultConstruction<int>> &m, int k, int v) { m.at(k) = v; }
void update(std::map<int, RemoveDefaultConstruction<int>> const &m, int k, int v) {
   //m.at(k) = v; // ERROR: Cannot change a const value
}

现场演示

我在这里看到 2 个选项

  1. 使地图常量并在更改某些内容时使用const_cast

    const std::map myMap;

    myMap[1] = 2;//不行,因为 const map

    (const_cast&>(myMap((.at(1( = 2;//OK with const_cast

  2. 创建一个包装类或派生一个只读取和更新现有值方法的自定义映射

我认为没有一种内置的方法可以仅使用更新值制作地图,并限制和插入。