-6

I have this problem of conversion with this code using c++11 standard:

#include<unordered_set>
struct B
{
   int x, y;
};

class A
{
   struct hash
   {
      std::size_t operator()( int* const a ) const
      {
         return std::hash<int>()( *a );
      }
   };

   struct equal_to
   {
      std::size_t operator()( int* const a, int* const b ) const
      {
         return std::equal_to<int>()( *a, *b );
      }
   };

   private:
      std::unordered_set< int*, hash, equal_to > set;

   public:
      void push( const B& b )
      {
         set.insert( &b.x );
      }
};

Anyone know why is that? I can I solve the problem removing the "const" modifier in the argument of "push". But I don't want it because argument "b" isn't modified.

Edit.: My simplification of code has produced a unreferenced adress. I've make a struct B remove it.

xgbuils
  • 115
  • 1
  • 9
  • 2
    Voting to reopen. The problem is in `set.insert(&a)`, where `a` has type `const int&`. The address of `a` has type "pointer to const int", but the set object is looking for a "pointer to (modifiable) int". That kind of `const` confusion is quite common, and warrants an answer. – Pete Becker May 13 '13 at 19:06
  • 2
    Unrelated to your question, but you are storing the address of an object that may be a temporary object in your set. Once the `a` that was passed to the `push` method has gone out of scope the address is invalid and could lead to heap corruption or application crashes later if it is referenced (by say your `equal_to` method). – pstrjds May 13 '13 at 19:08
  • Exactly what problem are you having? Is there an error message? If so, what is it? – Keith Thompson May 13 '13 at 19:32
  • The unrelated question: is there any important difference in passing int by value and passing int by const ref? – maverik May 13 '13 at 19:36
  • @maverik: not a relevant difference in this case – Mooing Duck May 13 '13 at 19:57

1 Answers1

2

The key of the set is declared as being a pointer-to-int, an int*. But this:

void push( const B& b )
{
    set.insert( &b.x );
}

is passing the address of a constant int, an int const*, hence the compiler error.

Removing the const from the argument would resolve the compiler error, as would making the key type an int const*, but both these solutions would:

  • permit some other part of the program, with non-const access to a B instance that was passed to push(), to change a value of one of the keys within the set and break the set invariant:

    A a;
    
    B b1{17, 22};
    B b2{30, 22};
    
    a.push(b1);
    a.push(b2);
    
    b1.x = 30;  // set no longer contains unique keys.
    
  • introduce a dependency of the set on the lifetime of the object referenced by b:

    A a;
    a.push({14, 23}); // a now contains a dangling pointer.
    

The safest solution is to store an int as the key, see http://ideone.com/KrykZw for online demo (thanks to bitmask for comment).


Possible solutions:

  1. Dynamically copy b.x. Or,
  2. Use int const* as the key. Or preferably (avoiding explicit dynamic allocation),
  3. Use int as the key, instead of an int* (see http://ideone.com/KrykZw)
Community
  • 1
  • 1
hmjd
  • 120,187
  • 20
  • 207
  • 252
  • Storing an `int*` in a (hash-)set (or a regular set for that matter), while allowing the pointed-to-object to change and having the hash function compute the hash based on the pointed-to-object, will not only exhibit undefined behaviour but also quite certainly crash in any implementation I can think of. Your 3rd solution is the only one that makes sense. (Note that `b.x` might change even if `b` is passed as const ref into `push`.) – bitmask May 13 '13 at 20:03
  • Thanks, 2 option is my favourite solution because I don't want to use copy-constructor. – xgbuils May 13 '13 at 20:15
  • @jefebrondem, what copy constructor? – hmjd May 13 '13 at 20:29