Search code examples
c++mutableunordered-set

Using mutable to allow modification of object in unordered_set


Please consider the following code:

#include <iostream>
#include <unordered_set>

struct MyStruct
{
    int x, y;
    double mutable z;

    MyStruct(int x, int y)
        : x{ x }, y{ y }, z{ 0.0 }
    {
    }
};

struct MyStructHash
{
    inline size_t operator()(MyStruct const &s) const
    {
        size_t ret = s.x;
        ret *= 2654435761U;
        return ret ^ s.y;
    }
};

struct MyStructEqual
{
    inline bool operator()(MyStruct const &s1, MyStruct const &s2) const
    {
        return s1.x == s2.x && s1.y == s2.y;
    }
};

int main()
{
    std::unordered_set<MyStruct, MyStructHash, MyStructEqual> set;
    auto pair = set.emplace(100, 200);

    if (pair.second)
        pair.first->z = 300.0;

    std::cout << set.begin()->z;
}

I am using mutable to allow modification of the member z of MyStruct. I would like to know if this is ok and legal, since the set is a) unordered and b) I am not using z for hashing or equality?


Solution

  • I would say this is a perfect use of the "Mutable" keyword.

    The mutable keyword is there to mark members that are not part of the "state" of the class (ie they are some form of cached or intermediate value that does not represent the logical state of the object).

    Your equality operator (as well as other comparators (or any function that serializes the data) (or function that generates a hash)) define the state of the object. Your equality comparitor does not use the member 'z' when it checks the logical state of the object so the member 'z' is not part of the state of the class and is therefore illegable to use the "mutable" accessor.

    Now saying that. I do think the code is very brittle to write this way. There is nothing in the class that stops a future maintainer accidentally making z part of the state of the class (ie adding it to the hash function) and thus breaking the pre-conditions of using it in std::unordered_set<>. So you should be very judicious in using this and spend a lot of time writing comments and unit tests to make sure the preconditions are maintained.

    I would also look into "@Andriy Tylychko" comment about breaking the class into a const part and a value part so that you could potentially use it as part of a std::unordered_map.