我同意其他答案,即在标准 C++ 中通常还不可能,但我们可以解决问题的受限版本。
由于这都是编译时编程,我们不能有可变状态,所以如果你愿意为每个状态变化使用一个新变量,那么这样的事情是可能的:
- hash_state1 = hash(type1)
- hash_state2 = hash(type2, hash_state1)
- hash_state3 = hash(type3, hash_state2)
“hash_state”实际上只是迄今为止我们散列的所有类型的唯一类型列表。作为散列新类型的结果,它还可以提供size_t 值。
如果我们寻求散列的类型已经存在于类型列表中,我们返回该类型的索引。
这需要相当多的样板:
- 确保类型在类型列表中是唯一的:我在这里使用了@Deduplicator 的答案:https://stackoverflow.com/a/56259838/27678
- 在唯一类型列表中查找类型
- 使用
if constexpr 检查类型是否在类型列表中 (C++17)
第 1 部分:一个独特的类型列表:
再次感谢@Deduplicator's answer here 这部分。由于依赖于 tuple-cat 的实现,以下代码通过在 O(log N) 时间内对类型列表进行查找来节省编译时性能。
代码的通用性几乎令人沮丧,但好的部分是它允许您使用任何通用类型列表(tuple,variant,自定义的东西)。
namespace detail {
template <template <class...> class TT, template <class...> class UU, class... Us>
auto pack(UU<Us...>)
-> std::tuple<TT<Us>...>;
template <template <class...> class TT, class... Ts>
auto unpack(std::tuple<TT<Ts>...>)
-> TT<Ts...>;
template <std::size_t N, class T>
using TET = std::tuple_element_t<N, T>;
template <std::size_t N, class T, std::size_t... Is>
auto remove_duplicates_pack_first(T, std::index_sequence<Is...>)
-> std::conditional_t<(... || (N > Is && std::is_same_v<TET<N, T>, TET<Is, T>>)), std::tuple<>, std::tuple<TET<N, T>>>;
template <template <class...> class TT, class... Ts, std::size_t... Is>
auto remove_duplicates(std::tuple<TT<Ts>...> t, std::index_sequence<Is...> is)
-> decltype(std::tuple_cat(remove_duplicates_pack_first<Is>(t, is)...));
template <template <class...> class TT, class... Ts>
auto remove_duplicates(TT<Ts...> t)
-> decltype(unpack<TT>(remove_duplicates<TT>(pack<TT>(t), std::make_index_sequence<sizeof...(Ts)>())));
}
template <class T>
using remove_duplicates_t = decltype(detail::remove_duplicates(std::declval<T>()));
接下来,我声明我自己的自定义类型列表以使用上述代码。你们大多数人以前见过的一个非常简单的空结构:
template<class...> struct typelist{};
第 2 部分:我们的“hash_state”
“hash_state”,我称之为hash_token:
template<size_t N, class...Ts>
struct hash_token
{
template<size_t M, class... Us>
constexpr bool operator ==(const hash_token<M, Us...>&)const{return N == M;}
constexpr size_t value() const{return N;}
};
简单地为哈希值封装一个size_t(你也可以通过value()函数访问它)和一个比较器来检查两个hash_tokens是否相同(因为你可以有两个不同的类型列表但相同的哈希值. 例如,如果您对int 进行哈希处理以获取令牌,然后将该令牌与您已哈希处理的令牌进行比较(int、float、char、int))。
第 3 部分:type_hash 函数
最后我们的type_hash函数:
template<class T, size_t N, class... Ts>
constexpr auto type_hash(T, hash_token<N, Ts...>) noexcept
{
if constexpr(std::is_same_v<remove_duplicates_t<typelist<Ts..., T>>, typelist<Ts...>>)
{
return hash_token<detail::index_of<T, Ts...>(), Ts...>{};
}
else
{
return hash_token<N+1, Ts..., T>{};
}
}
template<class T>
constexpr auto type_hash(T) noexcept
{
return hash_token<0, T>{};
}
第一个重载是针对泛型的;您已经“散列”了许多类型,并且想要再散列另一种类型。它检查你正在散列的类型是否已经散列,如果是,它返回唯一类型列表中类型的索引。
为了在类型列表中获取类型的索引,我使用简单的模板扩展来节省一些编译时模板实例化(避免递归查找):
// find the first index of T in Ts (assuming T is in Ts)
template<class T, class... Ts>
constexpr size_t index_of()
{
size_t index = 0;
size_t toReturn = 0;
using swallow = size_t[];
(void)swallow{0, (void(std::is_same_v<T, Ts> ? toReturn = index : index), ++index)...};
return toReturn;
}
type_hash 的第二个重载用于创建从 0 开始的初始 hash_token。
用法:
int main()
{
auto x = []{};
auto y = []{};
auto z = x;
std::cout << std::is_same_v<decltype(x), decltype(y)> << std::endl; // 0
std::cout << std::is_same_v<decltype(x), decltype(z)> << std::endl; // 1
constexpr auto xtoken = type_hash(x);
constexpr auto xytoken = type_hash(y, xtoken);
constexpr auto xyztoken = type_hash(z, xytoken);
std::cout << (xtoken == xytoken) << std::endl; // 0
std::cout << (xtoken == xyztoken) << std::endl; // 1
}
结论:
在很多代码中并不是很有用,但这可能有助于解决一些受限的元编程问题。