1

So, I hav a class that has a variable alignas(void*) bool failed inside. That failed variable contains an address.

    auto getFailed() {
        unsigned long long adress;
        memcpy(&adress, &(this->failed), sizeof(void*));
        return ((BaseA*)(void*)(adress))->failed;
    }

The above code works well, but I think its kind of ugly to do that memcpy thing

So I tried the following:

    auto getFailed() {
        return ((BaseA*)&(this->failed))->failed;
    }

In theorie this should work just fine, but it does not. I am not really sure why, and how to fix it.

Paul Gigel
  • 41
  • 4
  • 1
    Why is it a `bool`? It just asks for trouble. – HolyBlackCat Jun 28 '23 at 06:42
  • 2
    Please provide a [mcve] of the issue. But if you are intentionally breaking the (aliasing) rules of C++ then no wonder it doesn't work, the rules are not there just to make the Standard longer. The fix is to either use memcpy, or do not store `void*` inside `bool` in the first place, why would you? – Quimby Jun 28 '23 at 06:46
  • 1
    [std::bit_cast](https://en.cppreference.com/w/cpp/numeric/bit_cast) – Pepijn Kramer Jun 28 '23 at 06:52
  • why "ugly" ? This is purely subjective. If it works it is beautiful, no? – 463035818_is_not_an_ai Jun 28 '23 at 06:53
  • My understanding is that, if you don't have bitcast (see answer below), the key is to write a memcpy and code around it that is simple enough so the compiler can see through that the operation is trivial. Seeing the generated code is useful here. – alfC Jun 28 '23 at 07:18

1 Answers1

3

You cannot really type-pun a bool into an unsigned long long via memcpy (unless they have the same size). Putting alignas(void*) on a bool doesn't solve this, because you're reading bytes that are past its object representation. It is memory-safe to do because of padding in the surrounding struct, but it's still undefined behavior in C++.

See also: Why does std::memcpy (as an alternative to type-punning) not cause undefined behaviour? tl; dr: the only thing you can do with std::memcpy is copy values between two objects of the same type. Anything beyond that is a compiler extension.

On a side note, it would be much better to use std::uintptr_t instead of unsigned long long, because it is guaranteed to have the same size as void*.

Solution with std::bit_cast

Since C++20, there is a function that does this memcpy step for you:

auto getFailed() {
    return std::bit_cast<std::uintptr_t>(this->failed);
}

However, you cannot use it, because it requires sizeof(this->failed) == sizeof(std::uintptr_t). You would have to put your bool into a wrapper struct with the same size as unsigned long long instead of padding it via alignas:

struct {
    // important: Don't use alignas directly, because void* could have no alignment
    //            requirements despite being 8 bytes in size.
    //            In that case, we would have no padding.
    alignas(sizeof(void*)) bool value;
} failed;

However, this is still technically UB, because bit-casting padding bits is UB in most cases. You must bit-cast bits which are part of the value representation of an object, so we must add manual paddding:

alignas(void*) struct {
    bool value;
    char padding[sizeof(void*) - sizeof(bool)];
} failed;

Solution with reinterpret_cast

auto getFailed() {
    // your code, but C-style cast converted to reinterpret_cast
    return reinterpret_cast<BaseA*>(&this->failed)->failed;
}

Don't even consider this, it's undefined behavior. It would be a violation of strict aliasing to read a std::uintptr_t through a pointer that is actually pointing to bool. These two types cannot alias each other.

Jan Schultke
  • 17,446
  • 6
  • 47
  • 96