如何在编译器时动态获取元组内的变量类型C++?



我正在尝试编写一个流派哈希函数,以便在我将在程序运行时中使用的哈希表中使用。

但是我在这里遇到了一个问题,使用constexprstd::hash(( 函数。我想动态枚举我的元组值,但我无法通过在运算符重载方法中指定constexpr来做到这一点。

我做错了什么?

#include <iostream>
#include <unordered_map>
using llong = long long;
using ullong = unsigned long long;
using tlong = std::tuple<llong, llong>;
using tllong = std::tuple<llong, llong, llong>;
using tlllong = std::tuple<llong, llong, llong, llong>;
using tulong = std::tuple<ullong, ullong>;
using tullong = std::tuple<ullong, ullong, ullong>;
using tulllong = std::tuple<ullong, ullong, ullong, ullong>;
template<class Args>
struct KeyHash
{
constexpr std::size_t operator()(const Args &args) const
{
std::size_t seed = 1, _size = std::tuple_size<Args>::value;
for(auto i = 0ul; i< _size; i++)
{
seed += std::hash<std::tuple_element<(const std::size_t)i, Args>::type>()(std::get<i>(args)) + 0x2e3dbcd34 + (seed << 6) + (seed >> 4);
}
return seed;
}
};
template<class Args>
struct KeyEqual
{
bool operator()(const Args &v1, const Args &v2) const 
{
return (v1 == v2);
}
};
using  umtlong = std::unordered_map<tlong, int, KeyHash<tlong>, KeyEqual<tlong>>;
using  umtllong = std::unordered_map<tllong, int, KeyHash<tllong>, KeyEqual<tllong>>;
using  umtlllong = std::unordered_map<tlllong, int, KeyHash<tlllong>, KeyEqual<tlllong>>;
using  umtulong = std::unordered_map<tulong, int, KeyHash<tlong>, KeyEqual<tlong>>;
using  umtullong = std::unordered_map<tullong, int, KeyHash<tllong>, KeyEqual<tllong>>;
using  umtulllong = std::unordered_map<tulllong, int, KeyHash<tlllong>, KeyEqual<tlllong>>;
int main()
{
return 0;
}

错误,我在这里是

g++ hash-map-tuple-keys.cc -o hash-map-tuple-keys.o -std=c++17
hash-map-tuple-keys.cc:21:50: error: non-type template argument is not a constant expression
seed += std::hash<std::tuple_element<(const std::size_t)i, Args>::type>()(std::get<i>(args)) + 0x2e3dbcd34 + (seed << 6) + (seed >> 4);
^~~~~~~~~~~~~~~~~~~~
hash-map-tuple-keys.cc:21:69: note: read of non-const variable 'i' is not allowed in a constant expression
seed += std::hash<std::tuple_element<(const std::size_t)i, Args>::type>()(std::get<i>(args)) + 0x2e3dbcd34 + (seed << 6) + (seed >> 4);
^
hash-map-tuple-keys.cc:19:18: note: declared here
for(auto i = 0ul; i< _size; i++)
^
1 error generated.

constexpr函数中的表达式,即使被计算为常量表达式,仍然必须具有静态类型。因此,不能使用for循环,期望它在每次迭代中对不同类型的操作。

您可以改用折叠表达式(假设 C++17(。但要做到这一点,你需要一个参数包。您可以获取此信息(在元组的情况下(,例如通过使用通用 lambda 调用std::apply

在下面的代码中,除了std::apply之外,我还使用了KeyHash的部分特化,但这主要是为了方便起见,以便我Args并且不需要在 lambda 中使用std::remove_reference_t<decltype(args)>

template<class>
struct KeyHash;
template<class... Args>
struct KeyHash<std::tuple<Args...>>
{
constexpr std::size_t operator()(const std::tuple<Args...> &args) const
{
std::size_t seed = 1;
std::apply([&](const auto&... args){
(..., (seed += std::hash<Args>()(args) + 0x2e3dbcd34 + (seed << 6) + (seed >> 4)));
}, args);
return seed;
}
};

除了std::apply,您还可以使用std::integer_sequence.感谢@super使用它来修复我以前错误的代码:

template<class Args, class I = std::make_index_sequence<std::tuple_size_v<Args>>>
struct KeyHash;
template<class... Args, std::size_t... Is>
struct KeyHash<std::tuple<Args...>, std::index_sequence<Is...>>
{
constexpr std::size_t operator()(const std::tuple<Args...> &args) const
{
std::size_t seed = 1;
(..., (seed += std::hash<Args>()(std::get<Is>(args)) + 0x2e3dbcd34 + (seed << 6) + (seed >> 4)));
return seed;
}
};

为什么你的代码无法编译?

有关代码不起作用的原因,请参阅此答案。

溶液:

如果您使用的是 c++ 17 或更高版本,那么您可以使用折叠表达式(我相信其他人已经发布了有关它的答案(。

如果您使用的是 c++ 14,则在std::index_sequence上使用递归 constexpr 函数:

#include <iostream>
#include <unordered_map>
using llong = long long;
using ullong = unsigned long long;
using tlong = std::tuple<llong, llong>;
using tllong = std::tuple<llong, llong, llong>;
using tlllong = std::tuple<llong, llong, llong, llong>;
using tulong = std::tuple<ullong, ullong>;
using tullong = std::tuple<ullong, ullong, ullong>;
using tulllong = std::tuple<ullong, ullong, ullong, ullong>;
template <typename T>
struct tuple_to_index_seq {
using type = std::make_index_sequence<std::tuple_size<T>::value>;
};
template <typename T>
std::size_t calc_hash(std::size_t seed, const T& obj) {
return std::hash<T>{}(obj) + 0x2e3dbcd34 + (seed << 6) + (seed >> 4);
}
template <typename T>
constexpr std::size_t key_hash_impl(std::size_t seed, const T& tup, std::index_sequence<>) {
return seed;
}
template <typename T, size_t First, size_t... Rest>
constexpr std::size_t key_hash_impl(std::size_t seed, const T& tup, std::index_sequence<First, Rest...>) {
seed += calc_hash(seed, std::get<First>(tup));
return key_hash_impl(seed, tup, std::index_sequence<Rest...>{});
}
template<typename TupleType>
struct KeyHash
{
constexpr std::size_t operator()(TupleType tup) const
{
return key_hash_impl(1u, tup, typename tuple_to_index_seq<TupleType>::type{});
}
};
template<class Args>
struct KeyEqual
{
bool operator()(const Args& v1, const Args& v2) const
{
return (v1 == v2);
}
};
using  umtlong = std::unordered_map<tlong, int, KeyHash<tlong>, KeyEqual<tlong>>;
using  umtllong = std::unordered_map<tllong, int, KeyHash<tllong>, KeyEqual<tllong>>;
using  umtlllong = std::unordered_map<tlllong, int, KeyHash<tlllong>, KeyEqual<tlllong>>;
using  umtulong = std::unordered_map<tulong, int, KeyHash<tlong>, KeyEqual<tlong>>;
using  umtullong = std::unordered_map<tullong, int, KeyHash<tllong>, KeyEqual<tllong>>;
using  umtulllong = std::unordered_map<tulllong, int, KeyHash<tlllong>, KeyEqual<tlllong>>;
int main()
{
KeyHash<tlong> key;
auto tup = std::make_tuple(1LL, 2LL);
std::cout << key(tup);
umtlong my_map;
my_map.insert(std::make_pair(std::make_tuple(0LL, 0LL), 0));
return 0;
}

编译器资源管理器

最新更新