每种类型的编译时类型ID

compile time typeid for every type

本文关键字:类型 ID 编译 种类      更新时间:2023-10-16

我想要一个constexpr函数,它将为每个C++类型返回一个唯一的id,如下所示:

using typeid_t = uintptr_t;
template <typename T>
constexpr typeid_t type_id() noexcept
{
  return typeid_t(type_id<T>);
}
int main()
{
  ::std::cout << ::std::integral_constant<typeid_t, type_id<float>()>{} << ::std::endl;
  return 0;
}

但是我得到一个错误:

t.cpp: In function 'int main()':
t.cpp:23:69: error: conversion from pointer type 'typeid_t (*)() noexcept {aka long unsigned int (*)() noexcept}' to arithmetic type 'typeid_t {aka long unsigned int}' in a constant-expression
   ::std::cout << ::std::integral_constant<typeid_t, type_id<float>()>{} << ::std::endl;
                                                                     ^
t.cpp:23:69: note: in template argument for type 'long unsigned int' 

有没有解决方法或其他方法?

您可以使用一些技巧,如本答案所示。

甚至还有一个名为ctti的库使用了相同的技巧,它应该开箱即用。

static_assert(ctti::type_id<int>() != ctti::type_id<float>(), "compile-time type-id comparison");
constexpr auto hash = ctti::type_id<int>().hash();

另一种方法,这次涉及constexpr函数,是使用一个众所周知的哈希函数,如以下示例所示(我使用了 FNV v1a):

#include <cstdint>
#include <iostream>
static constexpr uint32_t offset = 2166136261u;
static constexpr uint32_t prime = 16777619u;
constexpr uint32_t helper(uint32_t partial, const char *str) {
    return str[0] == 0 ? partial : helper((partial^str[0])*prime, str+1);
}
constexpr uint32_t hash_str(const char *input) {
    return helper(offset, input);
}
struct MyClassA { static constexpr uint32_t type = hash_str("MyClassA"); };
struct MyClassB { static constexpr uint32_t type = hash_str("MyClassB"); };
int main() {
    std::cout << "MyClassA: " << MyClassA::type << std::endl;
    std::cout << "MyClassB: " << MyClassB::type << std::endl;
}

缺点:

  • 可能发生冲突
  • 容易出错(至少从我的角度来看)
  • 相当侵入性 A 解决

主要优点是,如果您需要在不同执行中类型相同(例如,如果您必须将它们存储在某个地方并在一段时间后再次使用它们),则可以使用此解决方案。

这不是一个constexpr函数,但是如果没有约束类型在多次执行中持久化,则可以使用CRTP习语作为替代方法来实现相同的结果。
它遵循一个最小的工作示例:

#include <cstddef>
#include <iostream>
struct BaseClass {
protected:
    static std::size_t next() noexcept {
        static std::size_t counter = 0;
        return counter++;
    }
};
template<class D>
struct Base: public BaseClass {
    static std::size_t type() noexcept {
        static std::size_t type_ = BaseClass::next();
        return type_;
    }
};
struct A: public Base<A> { };
struct B: public Base<B> { };
int main() {
    std::cout << A::type() << std::endl;
    std::cout << B::type() << std::endl;
}

这样,您就有一个基类,可以从中派生您希望拥有唯一标识符的所有类型。