Home > OS >  Can't insert non-const value in unordered_map of references
Can't insert non-const value in unordered_map of references

Time:11-21

I'm trying to create 2 std::unordered_map, one holds <A, int> and the second one holds <int&, A&>. I'll explain at the end why I want to do this if you're curious.

My problem is that k_i has value of type std::reference_wrapper, k_i.insert doesn't work. But if I make k_i to have value std::reference_wrapper<const A>, the insert works.

I just can't figure out why is this and I am curious.

<<<<<Edit: The thing is that find returns std::pair<const Ket, T> as stated by
Eljay in the comments. Because of this, the second std::unordered_map needs to have the value const. <<<<<

Code: Compiler: g version 10.1 Compile flags: -Wall -Wextra -std=c 20

#include <unordered_map>
#include <iostream>
#include <string>
#include <functional>

class A {
public:
  A(const int x) : x(x) {
    std::cout << "A::A(const int x) : x(" << x << ")\n";
  }

  A(const A& a) {
    std::cout << "A::A {" << x << "} (const A& a {" << a.x << "} )\n";
    x = a.x;
  }

  A(A&& a) {
    std::cout << "A::A {" << x << "} (A&& a {" << a.x << "} )\n";
    x = a.x;
  }

  A& operator=(const A& a) {
    std::cout << "A::operator= {" << x << "} (const A& a)\n";
    x = a.x;
    return *this;
  }

  A& operator=(A&& a) {
    std::cout << "A::operator= {" << x << "} (A&& a)\n";
    x = a.x;
    return *this;
  }

  ~A() {
    std::cout << "A::~A(" << x << ")\n";
  }

    friend std::ostream& operator<<(std::ostream& os, const A& dt);

  int x;
};

std::ostream& operator<<(std::ostream& os, const A& dt) {
    return os << dt.x;
}

template <typename K, typename V, typename... args>
void print_um(const std::unordered_map<K, V, args...> &umap) {
    for (const auto &[x, y] : umap) {
        std::cout << "(" << x << "," << std::ref(y).get() << "); ";
    }
    std::cout << "\n";
}

template <typename T>
struct MyHash {
    std::size_t operator()(T const& s) const noexcept {
        return std::hash<int>{}(std::ref(s).get());
    }
};

template <typename T>
struct MyEquals {
    constexpr bool operator()(const T &lhs, const T &rhs) const {
        return lhs == rhs;
    }
};

struct MyHash_A {
    std::size_t operator()(A const& s) const noexcept {
        return std::hash<int>{}(s.x);
    }
};

struct MyEquals_A {
    constexpr bool operator()(const A &lhs, const A &rhs) const {
        return lhs.x == rhs.x;
    }
};

int main() {

    std::unordered_map<A, int, MyHash_A, MyEquals_A> k_s;
    std::unordered_map<std::reference_wrapper<int>, std::reference_wrapper<const A>, MyHash<std::reference_wrapper<int>>, MyEquals<std::reference_wrapper<int>>> k_i;
{
    A a(5);
    std::cout << "1----\n";
    k_s[a] = 12;
    std::cout << "2----\n";
}
    std::cout << "3----\n";
    print_um<>(k_s);


    std::cout << "4----\n";
    A a(5);
    std::cout << "5----\n";
    auto it = k_s.find(a);
    std::cout << "6----\n";
    k_i.emplace((*it).second, (*it).first);
    // // k_i[(*it).second] = ref_name;


    std::cout << "7----\n";
    print_um<>(k_s);
    std::cout << "8----\n";
    print_um<>(k_i);

    std::cout << "9----\n";
    int x = 12;
    int &ref = x;
    auto is_there = k_i.find(ref);
    if (is_there != k_i.end()) {
        std::cout << "elem: " << (*is_there).second.get() << "\n";
    } else {
        std::cout << "why? :(\n";
    }
    std::cout << "10---\n";
    return 0;
}

As to why I create this code, I was thinking to be able to access some data by value or by key interchangeably (is there some better data structure? ). Like an username and a token, sometimes I have one, other times I have the other and using references I ensure that I don't waste space. Ofc, if one value has to change, I would invalidate the bucket position in the unordered_map because of the key, but I would treat that problem at a later date. Another motive is to learn some more C and test its limits (or mine).

CodePudding user response:

From UnorderedAssociativeContainer requirements:

For std::unordered_map and std::unordered_multimap the value type is std::pair<const Key, T>.

In your code k_s is unordered_map<A, int>, so the value type is pair<const A, int>. In here:

auto it = k_s.find(a);

you get a "pointer" to such pair, and type of (*it).first is const A.

Your k_i is unordered_map<..., ref<A>> and when you do insert here:

k_i.emplace(..., (*it).first);

you essentially attempt to initialize ref<A> with const A, which obviously cannot work.

When you change k_i type to unordered_map<..., ref<const A>>, then you initialize ref<const A> with const A, which is fine.

CodePudding user response:

Many have mentioned in the comment that Key type must be const. However the OP seems to wonder why the value type in k_i also need to be a const.

The reason for that is because you are referencing the key from k_s, which would be const A, and you can not reference it with a non-const reference.

To properly declare k_s, you might want to do something like:

std::unordered_map<
    std::reference_wrapper<decltype(k_s)::value_type::second_type>, 
    std::reference_wrapper<decltype(k_s)::value_type::first_type>,
    yourHash, yourComp
> k_s;

One alternative solution for you is to use another container that actually supports bidirectional lookup, such as Boost.Bimap.

  • Related