0

I am using this code to convert a pointer to a size_t, it is used in a ::std::hash function that should hash a given pointer at compile time, and since reinterpret_casts are not allowed in constexpr, I came up with the following solution, it works as expected but I want to know whether this can be considered a good idea. It would also be great if you could give me a few pointers on how portable this piece of code actually is.

union {
   size_t datr;
   void* pointer;
} dummy{.pointer = (void*) somePointer};
size_t pointerAsAsize_t = dummy.datr; // how portable is this?

Is there any better solution - as stated above, I want to create a ::std::hash<some_pointer*> that runs at compile time.

timrau
  • 22,578
  • 4
  • 51
  • 64
  • Can you test on more than one compiler and platform? – ruipacheco Apr 05 '19 at 11:07
  • 2
    Given that `.pointer` is set but never read it might be possible that the compiler never bothers to set it at all because it can assume you never read from `.datr` unless you wrote to it first. – Galik Apr 05 '19 at 11:12
  • 2
    "it works as expected" Not within constant evaluation (e.g. template argument, constexpr variable initializer), I believe. – cpplearner Apr 05 '19 at 11:20
  • Pointer values are generally not known at compile time. It isn't clear how you plan to hash unknown values. – n. m. could be an AI Apr 05 '19 at 11:45
  • 6
    [Accessing an inactive union member](https://stackoverflow.com/questions/11373203/accessing-inactive-union-member-and-undefined-behavior) is undefined behavior. – rustyx Apr 05 '19 at 11:54

2 Answers2

4

If you attempt to perform that in a constant expression context (ie: when the compiler is forced to call your code at compile time, such as the expression leading to an array size or a template argument), you will find that it will not compile. Constexpr execution requires that anything which would provoke UB at compile time will instead be ill-formed. And since your code provokes UB (by accessing a union member which is not active), it will fail to compile.

All three major compilers will fail on this in a constant-expression context. MSVC (surprisingly) gives the best, most direct error message:

(13): error C2131: expression did not evaluate to a constant
(9): note: failure was caused by accessing a non-active member of a union
(9): note: see usage of 'foo::Foo::integer'

Your code probably "works as expected" only because you're calling it when the compiler doesn't have to execute it at compile-time. Given GCC/Clang's errors, if you used it as the array size for an array on the stack, it probably invoked GCC/Clang's variable-length array language extension. If you turned that off, your code would likely not compile. My example made it a global array, which doesn't allow VLAs.


Is there any better solution

Nope. Even C++20's bit_cast will not be constexpr if you provide a pointer (or a type containing pointers) as either the source or destination. Compile-time pointers are real things, not just numbers that represent addresses; converting them to/from integers is simply not a reasonable activity at compile-time.

Compile-time pointers have to point to things that exist at compile-time. There is no way to know where they will point at runtime. So the idea of hashing the compile-time value of a pointer (even a pointer to a static/global object) is just doomed from the start.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
-2

I think on most of the plaftorms this code will work, however formally it can not. size_t is an unsigned type to hold the result sizeof(xxx) constructrion. There is no guarantee that is is big enough to store a pointer. You can add static_assert(sizeof(size_t) == sizeof(void*),"") or use std::intptr_t if it's available in your library.

Dmitry Gordon
  • 2,229
  • 12
  • 20
  • 3
    Its undefined behaviour to read from any `union` member except the last one to have been assigned to. Changing `datr`'s typd is not enough. – François Andrieux Apr 05 '19 at 11:39