2

I have an API that needs to be as simple as possible. At the same time, strings to certain functions are "well known", and so are always string literals, and can therefore be turned into CRC32 values at compile time, like so:

function(crc32("text"), ...);

The checksumming happens at compile-time using constexpr.

I want to simplify this API so that the people I work with don't have to know this pointless details. I want to make the compile-time checksumming happen inside the function, like so:

function("text", ...);

Doing the checksumming inside the inline function does not work because the function argument is no longer constexpr. The following code will fail to compile:

inline void function (const char* text, ...) {
    constexpr uint32_t hash = crc32(text); // does not work
}

This is for an emulated environment so its crucial that the checksumming happens at compile-time. There are several functions that work like this, and they will all benefit from a simplification of the API.

What options do I have to hide the fact that you have to remember to use the call to crc32?

Using rustyx idea, I made this:

struct AutoCRC {
    constexpr AutoCRC(const char* str) : hash{crc32(str)} {}
    constexpr AutoCRC(const uint32_t h) : hash{h} {}
    const uint32_t hash;
};

But it didn't work. Compared to using a constexpr CRC32 hash directly this almost doubled the binary size: 1488 -> 2696.

I have some limited C++20 access with GCC 9.2.

gonzo
  • 442
  • 4
  • 15
  • Do you want a user to be able to call the function at runtime too, with a checksum that gets computed at runtime? – Nicol Bolas Apr 18 '20 at 20:45
  • No, the checksumming absolutely has to happen at compile-time, as this is an emulated environment. If it really comes down to it I will keep the API as-is, but I am curious if there's a way to pass a const char* to a function and still get constexpr functionality. I will probably make separate functions that would accept dynamic strings if I have to. – gonzo Apr 18 '20 at 20:53
  • Macros are presumably out of the question? – dialer Apr 18 '20 at 21:00
  • It was my first thought to use macros, but that's why I came here - to see if there really was no other way. Using a macro will be my fallback. :) – gonzo Apr 18 '20 at 21:04
  • @gonzo: You cannot *make* the compiler do anything at compile-time unless you use that expression in a legit constant expression context (like a non-type template parameter). At least, not until C++20, which permits `consteval` functions that are always constant expression contexts. – Nicol Bolas Apr 18 '20 at 21:06
  • Making `crc32` `consteval` would require the calling function "`function`" to be `consteval` too. Maybe you *could* in theory use a template argument. That's already a terrible idea as it is. But it's getting even worse if you want a [string template argument](https://stackoverflow.com/a/15863804/653473). In the end, I think a macro is probably the most sane and understandable solution. – dialer Apr 18 '20 at 21:14

1 Answers1

0

Note that even in case of function(crc32("text"), ...) the context of the crc32("text") call isn't constexpr and so it isn't guaranteed to happen at compile time. You need consteval for such a guarantee.

Similar to the macro solution, you could wrap the string literal in a helper "checksummer" class that stores the string and its checksum, and accept that as an argument. This way the checksumming happens before calling the function and a good compiler will do it at compile time for strings that are known at compile time.

For example:

#include <string_view>

constexpr uint32_t checksum(std::string_view sv) noexcept {
    uint32_t sum = 0;
    for (unsigned char c : sv)
        sum += c;
    return sum;
}

struct StrWithSum {
    const char* str;
    const uint32_t sum;
    constexpr StrWithSum(const char* p) : str(p), sum(checksum(p)) {}
};

uint32_t do_something(StrWithSum str) {
    return str.sum + 2;
}

int main() {
    return do_something("ABC");
    static_assert(StrWithSum("ABC").sum == 198, "sum");
}

We can see that the do_something function compiles down to the + 2 operation in both GCC and clang:

_Z12do_something10StrWithSum: # do_something
  lea eax, [rsi + 2]
  ret

And the entire program to

main: # @main
  mov eax, 200
  ret

(not the case in VC++, unfortunately).

rustyx
  • 80,671
  • 25
  • 200
  • 267
  • Oh no, sorry, I want to be able to do the checksumming inside the function on the function argument - so that the API becomes leaner. Right now I am forced to call my function like this: function(crc32("string"), ...) which means I have to "remember" to use CRC32 - I've updated my question to explain better :) Sorry for the confusion! – gonzo Apr 18 '20 at 20:29
  • Ok, see my update... there isn't much that can be done unfortunately. – rustyx Apr 18 '20 at 21:28
  • Sadly, I wasn't able to reproduce this on RISC-V. I made a class that stored a 32-bit hash constructed from a constexpr constructor, and the binary doubled in size. These are tiny binaries, so it's not that hard to blow up the size. You can see my attempt in the bottom of the question text. – gonzo Apr 18 '20 at 23:35