Converting enums to pointers

J

James Aguilar

I have a class that's holding a list of pointers to pointers. It is an
implementation of an open addressed hashmap. It is homework, but I only
want help on this specific issue. Please do not tell me anything about
hashmaps, just about this small problem. Suppose I want to have an
enumeration that describes various states that a slot can have besides
having contents.

enum SpecStates { EMPTY = 0, DELETED = -1 };

However, the array that holds the information is an array of 'Record*'s. I
cannot assign EMPTY or DELETED to a slot without an explicit type cast (
(Record*) EMPTY ), since it involves a jump from int to Record*, which is
taken to be from int to void* to Record*. This is ugly and, perhaps,
suboptimal.

I have also considered

const Record* EMPTY = (Record*) 0;
const Record* DELETED = (Record*) 1;

But if I do that I have lost all the convenience of the enumerated type.

What is the correct way to accomplish what I am trying to do, assuming I
want no major design changes (for instance, I want no wrapper for the
Record, since, although that would work, it does not preserve the simplicity
I am seeking)?

James
 
R

Ron Natalie

James said:
const Record* EMPTY = (Record*) 0;

You declare EMPTY to be a null pointer here. This is legitimate.
const Record* DELETED = (Record*) 1;

This is implementation defined and may not universally work. Nothing
guarantees you can reinterpret cast an arbitrary integer to a pointer type.
But if I do that I have lost all the convenience of the enumerated type.
const Record DELETED;
would work, of course it allocates an unused Record object.
 
S

Siemel Naran

Ron Natalie said:
James Aguilar wrote:

You declare EMPTY to be a null pointer here. This is legitimate.


This is implementation defined and may not universally work. Nothing
guarantees you can reinterpret cast an arbitrary integer to a pointer type.
const Record DELETED;
would work, of course it allocates an unused Record object.

This is a fine solution, though it might be nice to use the extern keyword.
If you say

// a.cpp
const Record DELETED;
void a(std::vector<Record*>& v) {
v.push_back(&DELETED);
}

// b.cpp
const Record DELETED;
void b(std::vector<Record*>& v) {
v.push_back(&DELETED);
}

// main.cpp
#include "a.h"
#include "b.h"
int main() {
std::vector<Record*>& v;
a(v);
b(v);
}

then even though v[0] and v[1] are both deleted records, they have different
addresses and don't compare equal (ie. because we're comparing two different
pointers).

In other words, it is not safe to compare v[N] to &DELETED or &EMPTY to see
if the record is deleted or empty.

To get around this problem we could declare DELETED and EMPTY as extern.

// Record.h
class Record { ... };
extern const Record DELETED;

// Record.cpp
#include "Record.h"
const Record DELETED;

We could also make DELETED a static member of class Record (in which case we
don't need the extern keyword as class static variables are extern anyway),
though that seems a more sylistic issue.
 
R

Rolf Magnus

Ron said:
You declare EMPTY to be a null pointer here. This is legitimate.

Yes, and it doesn't actually need a cast.
This is implementation defined and may not universally work. Nothing
guarantees you can reinterpret cast an arbitrary integer to a pointer
type.

And nothing guarantess that the resulting pointer value is not one that
points to an existing object.
 
J

James Aguilar

Rolf Magnus said:
And nothing guarantess that the resulting pointer value is not one that
points to an existing object.

Oh, suppose that 1 is never to be dereferenced by the design and works only
as a flag? Is that guaranteed to work? I never actually want to look at
what's at 0x00000001, I just want to use it as a tag to say "This is not a
value you can use". Actually, in my implementation, I use '-1' for that,
not '1'. Is this a valid practice?
 
R

Ron Natalie

James said:
Oh, suppose that 1 is never to be dereferenced by the design and works only
as a flag? Is that guaranteed to work?

No! There is no guarantee you can convert any arbitrary integer to a
pointer. The only things that are guaranteed to work is an constant
0 (which converts to the null pointer) and the value of a valid pointer
value that was converted to some implementation defined "large enough"
integer type.
 
O

Old Wolf

James Aguilar said:
Oh, suppose that 1 is never to be dereferenced by the design and works only
as a flag? Is that guaranteed to work? I never actually want to look at
what's at 0x00000001, I just want to use it as a tag to say "This is not a
value you can use". Actually, in my implementation, I use '-1' for that,
not '1'. Is this a valid practice?

Some systems cause a hardware exception when you point to an address
that isn't aligned correctly (1 is hardly likely to have correct
alignment for a Record structure).

Other systems cause a hardware exception if you point to an
invalid address, or an address outside your process space.

You're less likely to get trouble with 1 than -1 (-1 will probably
be an invalid address on just about every system), and less
likely again to get trouble with sizeof(Record) instead of 1.

If you are writing a throwaway application then you could do
this "hack" but I would not recommend it for portable code.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
474,178
Messages
2,570,955
Members
47,509
Latest member
Jack116

Latest Threads

Top