"integer constant overflow" constexpr 中的警告
"integer constant overflow" warning in constexpr
我试图找到一个 constexpr 兼容的散列函数,用于在编译时散列字符串。字符串的数量真的很少(<10),我有一个单独的碰撞检查,所以算法可能远非完美。我在互联网上的某个地方找到了以下版本的 FNV1A:
static constexpr unsigned int Fnv1aBasis = 0x811C9DC5;
static constexpr unsigned int Fnv1aPrime = 0x01000193;
constexpr unsigned int hashFnv1a(const char *s, unsigned int h = Fnv1aBasis)
{
return !*s ? h : hashFnv1a(s + 1, (h ^ *s) * Fnv1aPrime);
}
但是当我在 MSVS 2015 中编译它时,我收到以下警告:
warning C4307: '*': integral constant overflow
因为函数中只有一个乘法,我假设警告来自 (h ^ *s) * Fnv1aPrime
。这是有道理的,因为将 0x811C9DC5
(Fnv1aBasis
) 与任何值相乘都会导致 32 位整数溢出。
有什么办法可以解决这个问题吗?我已经尝试了几个我发现的用于散列字符串的其他 constexpr 函数,但它们都有同样的问题。
如果您不介意溢出,那么只需关闭警告即可。无符号整数算法保证是模 2n 算法,其中 n 是位数值表示,所以无论如何这都是明确定义的。警告是一个愚蠢的警告;它警告您您正在使用无符号整数的主要功能。
我发现对于函数的局部 #pragma warning( disable: 4307 )
,每次 使用 函数时仍然会出现警告。
此重写消除了 32 位哈希函数的警告:
constexpr auto hashFnv1a( char const* s, unsigned h = Fnv1aBasis )
-> unsigned
{
return !*s ? h : hashFnv1a(s + 1, static_cast<unsigned>( 1ULL*(h ^ *s) * Fnv1aPrime ));
}
即使是广泛的谷歌搜索也没有找到任何方法来禁用关于无符号值溢出的愚蠢警告,同时为有符号值保留它,因此要处理 64 位哈希函数,看来唯一的办法是实现constexpr
64 位无符号乘法函数。因为它是 constexpr
,所以它是否特别有效并不重要。所以:
#include <stdint.h>
namespace b32 {
static constexpr uint32_t Fnv1aBasis = 0x811C9DC5u;
static constexpr uint32_t Fnv1aPrime = 0x01000193u;
constexpr auto hashFnv1a( char const* s, uint32_t h = Fnv1aBasis )
-> uint32_t
{ return !*s ? h : hashFnv1a(s + 1, static_cast<uint32_t>( 1ULL*(h ^ *s)*Fnv1aPrime )); }
} // namespace b32
namespace b64 {
static constexpr uint64_t Fnv1aBasis = 0xCBF29CE484222325uLL;
static constexpr uint64_t Fnv1aPrime = 0x100000001B3uLL;
constexpr auto lo( uint64_t x )
-> uint64_t
{ return x & uint32_t( -1 ); }
constexpr auto hi( uint64_t x )
-> uint64_t
{ return x >> 32; }
constexpr auto mulu64( uint64_t a, uint64_t b )
-> uint64_t
{
return 0
+ (lo( a )*lo( b ) & uint32_t(-1))
+ (
(
(
(
(
hi( lo( a )*lo( b ) ) +
lo( a )*hi( b )
)
& uint32_t(-1)
)
+ hi( a )*lo( b )
)
& uint32_t(-1)
)
<< 32
);
}
constexpr auto hashFnv1a( char const* s, uint64_t h = Fnv1aBasis )
-> uint64_t
{ return !*s ? h : hashFnv1a( s + 1, mulu64( h ^ *s, Fnv1aPrime ) ); }
} // namepace b64
#include <assert.h>
#include <iostream>
using namespace std;
auto main()
-> int
{
constexpr auto x = b64::mulu64( b64::Fnv1aBasis, b64::Fnv1aPrime );
#ifdef _MSC_VER
# pragma warning( push )
# pragma warning( disable: 4307 )
constexpr auto y = b64::Fnv1aBasis*b64::Fnv1aPrime;
# pragma warning( pop )
#else
constexpr auto y = b64::Fnv1aBasis*b64::Fnv1aPrime;
#endif
cout << x << endl;
cout << y << endl;
assert( x == y );
static constexpr const char* const s = "blah!";
constexpr unsigned xs = b32::hashFnv1a( s );
constexpr uint64_t ys = b64::hashFnv1a( s );
int a[1 + xs%2]; (void) a;
int b[1 + ys%2]; (void) b;
}
您可以显式转换为 unsigned long long
并返回,如下所示:
constexpr unsigned int hashFnv1b(const char *s, unsigned int h = Fnv1aBasis)
{
return !*s
? h
: hashFnv1b(
s + 1,
static_cast<unsigned int>(
(h ^ *s) * static_cast<unsigned long long>(Fnv1aPrime)));
}
这个stops the warning in my live demo(第20行触发它,第21行没有)。
另一种方法是将其包裹在宏中并使用__pragma关闭警告:
#include <type_traits>
#if _MSC_VER
#define FNV_HASH( str ) \
__pragma( warning( push ) ) \
__pragma( warning( disable: 4307 ) ) \
std::integral_constant<uint64_t, hashFnv1a( str )>::value \
__pragma( warning( pop ) )
#else
#define FNV_HASH( str ) std::integral_constant<uint64_t, hashFnv1a( str )>::value
#endif
std::integral_constant 强制编译器在编译时计算表达式,否则它在编译时上下文之外是可选的。
对于 64 位版本,这比实现您自己的 constexpr 64 位乘法要容易一些。
我试图找到一个 constexpr 兼容的散列函数,用于在编译时散列字符串。字符串的数量真的很少(<10),我有一个单独的碰撞检查,所以算法可能远非完美。我在互联网上的某个地方找到了以下版本的 FNV1A:
static constexpr unsigned int Fnv1aBasis = 0x811C9DC5;
static constexpr unsigned int Fnv1aPrime = 0x01000193;
constexpr unsigned int hashFnv1a(const char *s, unsigned int h = Fnv1aBasis)
{
return !*s ? h : hashFnv1a(s + 1, (h ^ *s) * Fnv1aPrime);
}
但是当我在 MSVS 2015 中编译它时,我收到以下警告:
warning C4307: '*': integral constant overflow
因为函数中只有一个乘法,我假设警告来自 (h ^ *s) * Fnv1aPrime
。这是有道理的,因为将 0x811C9DC5
(Fnv1aBasis
) 与任何值相乘都会导致 32 位整数溢出。
有什么办法可以解决这个问题吗?我已经尝试了几个我发现的用于散列字符串的其他 constexpr 函数,但它们都有同样的问题。
如果您不介意溢出,那么只需关闭警告即可。无符号整数算法保证是模 2n 算法,其中 n 是位数值表示,所以无论如何这都是明确定义的。警告是一个愚蠢的警告;它警告您您正在使用无符号整数的主要功能。
我发现对于函数的局部 #pragma warning( disable: 4307 )
,每次 使用 函数时仍然会出现警告。
此重写消除了 32 位哈希函数的警告:
constexpr auto hashFnv1a( char const* s, unsigned h = Fnv1aBasis )
-> unsigned
{
return !*s ? h : hashFnv1a(s + 1, static_cast<unsigned>( 1ULL*(h ^ *s) * Fnv1aPrime ));
}
即使是广泛的谷歌搜索也没有找到任何方法来禁用关于无符号值溢出的愚蠢警告,同时为有符号值保留它,因此要处理 64 位哈希函数,看来唯一的办法是实现constexpr
64 位无符号乘法函数。因为它是 constexpr
,所以它是否特别有效并不重要。所以:
#include <stdint.h>
namespace b32 {
static constexpr uint32_t Fnv1aBasis = 0x811C9DC5u;
static constexpr uint32_t Fnv1aPrime = 0x01000193u;
constexpr auto hashFnv1a( char const* s, uint32_t h = Fnv1aBasis )
-> uint32_t
{ return !*s ? h : hashFnv1a(s + 1, static_cast<uint32_t>( 1ULL*(h ^ *s)*Fnv1aPrime )); }
} // namespace b32
namespace b64 {
static constexpr uint64_t Fnv1aBasis = 0xCBF29CE484222325uLL;
static constexpr uint64_t Fnv1aPrime = 0x100000001B3uLL;
constexpr auto lo( uint64_t x )
-> uint64_t
{ return x & uint32_t( -1 ); }
constexpr auto hi( uint64_t x )
-> uint64_t
{ return x >> 32; }
constexpr auto mulu64( uint64_t a, uint64_t b )
-> uint64_t
{
return 0
+ (lo( a )*lo( b ) & uint32_t(-1))
+ (
(
(
(
(
hi( lo( a )*lo( b ) ) +
lo( a )*hi( b )
)
& uint32_t(-1)
)
+ hi( a )*lo( b )
)
& uint32_t(-1)
)
<< 32
);
}
constexpr auto hashFnv1a( char const* s, uint64_t h = Fnv1aBasis )
-> uint64_t
{ return !*s ? h : hashFnv1a( s + 1, mulu64( h ^ *s, Fnv1aPrime ) ); }
} // namepace b64
#include <assert.h>
#include <iostream>
using namespace std;
auto main()
-> int
{
constexpr auto x = b64::mulu64( b64::Fnv1aBasis, b64::Fnv1aPrime );
#ifdef _MSC_VER
# pragma warning( push )
# pragma warning( disable: 4307 )
constexpr auto y = b64::Fnv1aBasis*b64::Fnv1aPrime;
# pragma warning( pop )
#else
constexpr auto y = b64::Fnv1aBasis*b64::Fnv1aPrime;
#endif
cout << x << endl;
cout << y << endl;
assert( x == y );
static constexpr const char* const s = "blah!";
constexpr unsigned xs = b32::hashFnv1a( s );
constexpr uint64_t ys = b64::hashFnv1a( s );
int a[1 + xs%2]; (void) a;
int b[1 + ys%2]; (void) b;
}
您可以显式转换为 unsigned long long
并返回,如下所示:
constexpr unsigned int hashFnv1b(const char *s, unsigned int h = Fnv1aBasis)
{
return !*s
? h
: hashFnv1b(
s + 1,
static_cast<unsigned int>(
(h ^ *s) * static_cast<unsigned long long>(Fnv1aPrime)));
}
这个stops the warning in my live demo(第20行触发它,第21行没有)。
另一种方法是将其包裹在宏中并使用__pragma关闭警告:
#include <type_traits>
#if _MSC_VER
#define FNV_HASH( str ) \
__pragma( warning( push ) ) \
__pragma( warning( disable: 4307 ) ) \
std::integral_constant<uint64_t, hashFnv1a( str )>::value \
__pragma( warning( pop ) )
#else
#define FNV_HASH( str ) std::integral_constant<uint64_t, hashFnv1a( str )>::value
#endif
std::integral_constant 强制编译器在编译时计算表达式,否则它在编译时上下文之外是可选的。
对于 64 位版本,这比实现您自己的 constexpr 64 位乘法要容易一些。