Home > database >  C 17 - constexpr byte iteration for trivial types
C 17 - constexpr byte iteration for trivial types

Time:10-21

Given my prototype for a simple hashing method:

template <typename _iterator>
constexpr uint32_t hash_impl(_iterator buf, size_t len);

Generating constexpr hashes for simple strings is pretty trivial:

template <char const* str>
constexpr uint32_t generate()
{
    constexpr std::string_view strView = str;
    return hash_impl(str, strView.size());
}
constexpr uint32_t generate(const std::string_view& str)
{
    return hash_impl(str.begin(), str.size());
}

constexpr static char str1[] = "Hello World!";
constexpr crc32::hash hash1 = crc32::generate<str1>();

constexpr std::string_view str2("Hello World!");
constexpr crc32::hash hash2 = crc32::generate(str2);

I'd also like to generate constexpr hashes for a variety of simple (POD and trivial structs) types too. However, I'm not sure how to get the byte representation of these types in a constexpr-friendly way.

My naive implementation:

template <typename T, typename = std::enable_if_t< std::is_standard_layout_v<T> && !std::is_pointer_v<T> >>
constexpr uint32_t generate(const T& value)
{
    return hash_impl(reinterpret_cast<const std::byte*>(&value), sizeof(T));
}

falls over because &value and reinterpret_cast<> breaks constexpr rules. I've searched for a workaround, but other answers on the site indicate that it's not possible.

Worst case scenario, I can manually check for and hash specific types as so:

template <typename T, typename = std::enable_if_t< std::is_standard_layout_v<T> && !std::is_pointer_v<T> >>
constexpr uint32_t generate(const T& value)
{
    if constexpr (std::is_same_v<T, int32_t> || std::is_same_v<T, uint32_t>)
    {
        char buf[] =
        {
            static_cast<char>(value >>  0),
            static_cast<char>(value >>  8),
            static_cast<char>(value >> 16),
            static_cast<char>(value >> 24)
        };
        return generate(buf, 4);
    }
    else if constexpr (/* etc... */)
    // ...
}

but this falls over as soon as I try to implement this for something like a float (which has no bitwise operators) or for trivial custom types (eg, struct foo { int a; }; unless I write an extra code block for foo).

I feel like I must be overlooking something simple, but I can't find any stl utility or think of any fancy template trick that would suit. Perhaps there's something in C 20 or C 23 that I'm not aware of?

CodePudding user response:

It is not possible in C 17. In C 20 you can use std::bit_cast to get the object representation of a trivially copyable type:

auto object_representation = std::bit_cast<std::array<std::byte, sizeof(T)>>(value);

(Technically you can argue about whether or not it is guaranteed that std::array has no additional padding/members that would make this ill-formed, but in practice that is not a concern.)

You can then pass the array as a pointer/size pair to the hash implementation. You should probably also add

static_assert(std::has_unique_object_representations_v<T>);

If the assertion fails you will have no guarantee that objects with same value will have the same object representation and same hash.


Btw. std::is_standard_layout_v is not the property you need here. The property that you need to verify is std::is_trivially_copyable_v. Being standard-layout is neither sufficient nor necessary to be able to inspect and use the object representation like this.

  • Related