2

After some head-scratching, I've managed to constexpr-ize the Jesteress hashing algorithm. The compiler, however, refuses to generate a constant out of a cjesteress() call, such as in std::cout << cjesteress("test) << std::endl;, but generates code for the call with both clang-3.3 and gcc-4.8.1. What did I do wrong?

#include <cstdint>

#include <cstring>

#include <string>

#define ROL(x, n) (((x) << (n)) | ((x) >> (32-(n))))

namespace detail
{

constexpr std::uint32_t const PRIME(709607u);

//////////////////////////////////////////////////////////////////////////////
inline constexpr std::size_t cstrlen(char const* const p,
  std::size_t const s = 0)
{
  return *p ? cstrlen(p + 1, s + 1) : s;
}

//////////////////////////////////////////////////////////////////////////////
inline constexpr std::uint32_t case4(char const*& p, std::size_t const wrdlen,
  std::uint32_t hash32)
{
  return wrdlen & sizeof(std::uint32_t)
    ? hash32 = (hash32 ^ *(std::uint32_t*)p) * PRIME,
      p += sizeof(std::uint32_t),
      hash32
    : hash32;
}

//////////////////////////////////////////////////////////////////////////////
inline constexpr std::uint32_t case2(char const*& p, std::size_t const wrdlen,
  std::uint32_t hash32)
{
  return wrdlen & sizeof(std::uint16_t)
    ? hash32 = (hash32 ^ *(std::uint16_t*)p) * PRIME,
      p += sizeof(std::uint16_t),
      hash32
    : hash32;
}

//////////////////////////////////////////////////////////////////////////////
inline constexpr std::uint32_t case1(char const* p, std::size_t const wrdlen,
  std::uint32_t hash32)
{
  return wrdlen & 1
    ? hash32 = (hash32 ^ *p) * PRIME
    : hash32;
}

//////////////////////////////////////////////////////////////////////////////
inline constexpr std::uint32_t cjesteress(char const* p, std::size_t wrdlen,
  std::uint32_t hash32)
{
  return wrdlen >= 2 * sizeof(std::uint32_t)
    ? cjesteress(p + 2 * sizeof(std::uint32_t),
        wrdlen - 2 * sizeof(std::uint32_t),
        (hash32 ^ (ROL(*(std::uint32_t *)p, 5) ^ *(std::uint32_t *)(p + 4)))
          * ::detail::PRIME)
    : (hash32 = ::detail::case4(p, wrdlen, hash32),
      hash32 = ::detail::case2(p, wrdlen, hash32),
      hash32 = ::detail::case1(p, wrdlen, hash32),
      hash32 ^ (hash32 >> 16));
}

}

//////////////////////////////////////////////////////////////////////////////
inline constexpr std::uint32_t cjesteress(char const* const p)
{
  return ::detail::cjesteress(p, ::detail::cstrlen(p), 2166136261u);
}

//////////////////////////////////////////////////////////////////////////////
inline constexpr std::uint32_t cjesteress(char const* const p,
  std::size_t const wrdlen)
{
  return ::detail::cjesteress(p, wrdlen, 2166136261u);
}

The error is a cryptic:

testjesteress.cpp:16:33:   in constexpr expansion of
'cjesteress(((const char*)"1234567890"))'
../resource/jesteress.hpp:78:67:   in constexpr expansion of
'detail::cjesteress(((const char*)p), detail::cstrlen(((const
char*)p), 0ul), 2166136261u)' testjesteress.cpp:16:33: error:
accessing value of '"1234567890"' through a 'uint32_t {aka unsigned
int}' glvalue in a constant expression
     case cjesteress("1234567890"):
                                 ^

EDIT: Here is a working implementation, thanks to all:

#include <cstdint>

#include <cstring>

#define ROL(x, n) (((x) << (n)) | ((x) >> (32-(n))))

namespace detail
{

static constexpr std::uint32_t const PRIME(709607u);

//////////////////////////////////////////////////////////////////////////////
inline constexpr std::size_t cstrlen(char const* const p,
  std::size_t const s = 0)
{
  return *p ? cstrlen(p + 1, s + 1) : s;
}

//////////////////////////////////////////////////////////////////////////////
inline constexpr std::uint32_t p2u32(char const* const p)
{
  return p[0] | p[1] << 8 | p[2] << 16 | p[3] << 24;
}

//////////////////////////////////////////////////////////////////////////////
inline constexpr std::uint16_t p2u16(char const* const p)
{
  return p[0] | p[1] << 8;
}

//////////////////////////////////////////////////////////////////////////////
inline constexpr std::uint32_t case0(std::uint32_t const hash32)
{
  return hash32 ^ (hash32 >> 16);
}

//////////////////////////////////////////////////////////////////////////////
inline constexpr std::uint32_t case1(char const* const p,
  std::size_t const wrdlen, std::uint32_t const hash32)
{
  return wrdlen & 1
    ? case0((hash32 ^ *p) * PRIME)
    : case0(hash32);
}

//////////////////////////////////////////////////////////////////////////////
inline constexpr std::uint32_t case2(char const* const p,
  std::size_t const wrdlen, std::uint32_t const hash32)
{
  return wrdlen & sizeof(std::uint16_t)
    ? case1(p + sizeof(std::uint16_t), wrdlen, (hash32 ^ p2u16(p)) * PRIME)
    : case1(p, wrdlen, hash32);
}

//////////////////////////////////////////////////////////////////////////////
inline constexpr std::uint32_t case4(char const* const p,
  std::size_t const wrdlen, std::uint32_t const hash32)
{
  return wrdlen & sizeof(std::uint32_t)
    ? case2(p + sizeof(std::uint32_t), wrdlen, (hash32 ^ p2u32(p)) * PRIME)
    : case2(p, wrdlen, hash32);
}

//////////////////////////////////////////////////////////////////////////////
inline constexpr std::uint32_t cjesteress(char const* const p,
  std::size_t const wrdlen, std::uint32_t const hash32)
{
  return wrdlen >= 2 * sizeof(std::uint32_t)
    ? cjesteress(p + 2 * sizeof(std::uint32_t),
        wrdlen - 2 * sizeof(std::uint32_t),
        (hash32 ^ (ROL(p2u32(p), 5) ^ p2u32(p + 4)))
          * ::detail::PRIME)
    : ::detail::case4(p, wrdlen, hash32);
}

}

//////////////////////////////////////////////////////////////////////////////
inline constexpr std::uint32_t cjesteress(char const* const p)
{
  return ::detail::cjesteress(p, ::detail::cstrlen(p), 2166136261u);
}

//////////////////////////////////////////////////////////////////////////////
inline constexpr std::uint32_t cjesteress(char const* const p,
  std::size_t const wrdlen)
{
  return ::detail::cjesteress(p, wrdlen, 2166136261u);
}
user1095108
  • 14,119
  • 9
  • 58
  • 116
  • 1
    you are not showing the entire segment, where is the call to ` case cjesteress("1234567890"):`? – TemplateRex Aug 20 '13 at 21:07
  • @TemplateRex I wanted to give a hint, as to what might be the problem. The code compiles ok, but using it in a switch statement produces an error. – user1095108 Aug 20 '13 at 21:15
  • 2
    Compiling is only 1/2 of the job, since constexpr can choose between runtime and compile-time. Is the `switch` itself part of a `constexpr`? because that will only work in C++14 (clang -std=c++1y) – TemplateRex Aug 20 '13 at 21:25
  • No, the `switch` was in a test `.cpp` file, not in the implementation of the algorithm. I.e., I've posted the whole implementation. – user1095108 Aug 20 '13 at 21:27

1 Answers1

4

My guess is that the compiler doesn't like you accessing characters in a string by casting parts of it to a uint32_t (as it violates strict aliasing and is undefined behavior, at the very least in terms of endianness):

(hash32 ^ (ROL(*(std::uint32_t *)p, 5) ^ *(std::uint32_t *)(p + 4)))
          * ::detail::PRIME)

If you replaced those casts with a constexpr function that generated a uint32 instead, you should be good to go.

MSN
  • 53,214
  • 7
  • 75
  • 105