PaperBirdMaster
PaperBirdMaster

Reputation: 13298

constexpr function that keeps multiplying a number until "it is big enough"

Ok, I completely messed up this question, first for the typo and second because my oversimplified minimum example didn't have any problem at all. I was considering to delete the question but seeing that it have two answers with fairly good acceptance I will edit with the not oversimplified code. I'm sorry for all the mess1.

The following code creates an specializaton for std::hash:

#include <bit>
#include <bitset>

struct test{ const char *const s; std::size_t n; };

template<>
struct std::hash<test>
{
    constexpr std::size_t text_hash(const char *const text) const noexcept
    {
        auto result = ~std::size_t{};
        auto shift = 0ul;
        constexpr auto length = sizeof(result) * 8;

        for (const char *c = text; *c; ++c)
        {
            result ^= (std::size_t(*c) << shift);
            shift = ((shift + 8) % length);
        }

        return result;
    }
    constexpr std::size_t expand_number(std::size_t number) const noexcept
    {
        while (std::bit_width(number) < 64)
            number *= 7ul;

        return number;
    }
    constexpr std::size_t operator()(const test &t) const noexcept
    {
        return text_hash(t.s) ^ expand_number(t.n);
    }
};

Used as follows, does not compile:

constexpr std::hash<test> HASH;
#define TEXT(T) HASH(test{T, __COUNTER__})

int main()
{
    constexpr auto v = TEXT("Test");

    return 0;
}

The error shown by the compiler (Compiler Explorer link) is:

<source>:41:24:   in 'constexpr' expansion of 'HASH.std::hash<test>::operator()(test{((const char*)"Test"), 0})'
<source>:32:46:   in 'constexpr' expansion of '((const std::hash<test>*)this)->std::hash<test>::expand_number(((std::size_t)t.test::n))'
<source>:25:9: error: 'constexpr' loop iteration count exceeds limit of 262144 (use '-fconstexpr-loop-limit=' to increase the limit)
   25 |         while (std::bit_width(number) < 64)
      |         ^~~~~

Non-constexpr version works fine:

int main()
{
    auto v = TEXT("Test");

    return 0;
}

So why is this happening? How do I solve it?


1For anyone who wants to see the previous code, which compiles without any issue, here it is.

std::size_t A(std::size_t number) noexcept // Number shouldn't be 0
{
    while (std::bit_width(number) < 64)
        number *= 7ul;

    return number;
}

constexpr std::size_t B(std::size_t number) noexcept // Number shouldn't be 0
{
    while (std::bit_width(number) < 64)
        number *= 7ul;

    return number;
}

int main()
{
    auto a = A(1);
    constexpr auto b = B(1);

    return 0;
}

Upvotes: 1

Views: 222

Answers (3)

Filipp
Filipp

Reputation: 2040

__COUNTER__ has an initial value of 0, so clearly your loop never terminates.

https://godbolt.org/z/jc11bd636

Upvotes: 0

Adrian McCarthy
Adrian McCarthy

Reputation: 48021

If Ted Lyngmo's assertion about std::size_t being less than 64 for your implementation is correct, then clearly he's got the right answer.

But if you encounter the problem when compiling for a 64-bit target, it's possible the loop count that's exceeded is in the implementation of std::bit_width rather than the loop in B.

I peeked at an implementation of std::bit_width:

  • When it's not consteval, it uses intrinsic instructions.
  • When it is consteval it falls back to a loop to compute the result.

Perhaps there's a bug in the fallback implementation for the library you're using and it's that loop that's exceeding the maximum iterations. Since that is used only in a constexpr context, it would match the behavior observed.

Upvotes: 2

Ted Lyngmo
Ted Lyngmo

Reputation: 117832

std::size_t is 32 (or less than 64 bit) bit in your environment so std::bit_width(number) can be at most 32 - and the condition in your loop will therefore always be true:

while (std::bit_width(number) < 64) // always true if size_t has less than 64 bit

One adaptation that will not try to make it bigger than the number of bits in std::size_t:

#include <limits>

constexpr std::size_t B(std::size_t number) noexcept
{
    while (std::bit_width(number) < std::numeric_limits<std::size_t>::digits)
//                                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
//                                      the number of bits in a size_t
        number *= 7ul;

    return number;
}

Upvotes: 9

Related Questions