给定以下内容:
template<typename T>
class A
{
public:
static const unsigned int ID = ?;
};
我希望 ID 为每个 T 生成一个唯一的编译时 ID。我考虑过__COUNTER__
和提升PP库,但到目前为止还没有成功。我怎样才能做到这一点?
编辑:ID 必须可用作开关语句中的大小写
Edit2:所有基于静态方法或成员地址的答案都是不正确的。尽管它们确实创建了一个唯一的 ID,但它们不会在编译时解析,因此不能用作 switch 语句的情况。
假设一个符合标准的编译器(关于一个定义规则(,这就足够了:
template<typename T>
class A
{
public:
static char ID_storage;
static const void * const ID;
};
template<typename T> char A<T>::ID_storage;
template<typename T> const void * const A<T>::ID= &A<T>::ID_storage;
从C++标准 3.2.5 一个定义规则 [basic.def.odr](粗体强调我的(:
。如果 D 是模板,并且在多个翻译中定义 单位,则适用上述列表中的最后四个要求 模板中使用的模板封闭范围的名称 定义 (14.6.3(,以及从属名称 实例化 (14.6.2(。如果 D 的定义满足所有这些 要求,那么程序的行为就好像有一个 D的定义。如果 D 的定义不满足这些要求 要求,则行为是未定义的。
我通常使用的是这样的:
template<typename>
void type_id(){}
using type_id_t = void(*)();
由于函数的每个实例化都有自己的地址,因此您可以使用该地址来标识类型:
// Work at compile time
constexpr type_id_t int_id = type_id<int>;
// Work at runtime too
std::map<type_id_t, std::any> types;
types[type_id<int>] = 4;
types[type_id<std::string>] = "values"s
// Find values
auto it = types.find(type_id<int>);
if (it != types.end()) {
// Found it!
}
可以使用此答案中的代码从字符串生成编译时哈希。
如果可以修改模板以包含一个额外的整数并使用宏声明变量:
template<typename T, int ID> struct A
{
static const int id = ID;
};
#define DECLARE_A(x) A<x, COMPILE_TIME_CRC32_STR(#x)>
使用此宏进行类型声明,id 成员包含类型名称的哈希。例如:
int main()
{
DECLARE_A(int) a;
DECLARE_A(double) b;
DECLARE_A(float) c;
switch(a.id)
{
case DECLARE_A(int)::id:
cout << "int" << endl;
break;
case DECLARE_A(double)::id:
cout << "double" << endl;
break;
case DECLARE_A(float)::id:
cout << "float" << endl;
break;
};
return 0;
}
当类型名称转换为字符串时,对类型名称文本的任何修改都会导致不同的 id。例如:
static_assert(DECLARE_A(size_t)::id != DECLARE_A(std::size_t)::id, "");
另一个缺点是由于可能发生哈希冲突。
对我来说似乎很好:
template<typename T>
class Counted
{
public:
static int id()
{
static int v;
return (int)&v;
}
};
#include <iostream>
int main()
{
std::cout<<"Counted<int>::id()="<<Counted<int>::id()<<std::endl;
std::cout<<"Counted<char>::id()="<<Counted<char>::id()<<std::endl;
}
使用静态函数的内存地址。
template<typename T>
class A {
public:
static void ID() {}
};
(&(A<int>::ID))
将与(&(A<char>::ID))
等不同。
使用此常量表达式计数器:
template <class T>
class A
{
public:
static constexpr int ID() { return next(); }
};
class DUMMY { };
int main() {
std::cout << A<char>::ID() << std::endl;
std::cout << A<int>::ID() << std::endl;
std::cout << A<BETA>::ID() << std::endl;
std::cout << A<BETA>::ID() << std::endl;
return 0;
}
产出:(海湾合作委员会,C++14(
1
2
3
3
缺点是您需要猜测派生类数的上限,常量表达式计数器才能正常工作。
我最近遇到了这个确切的问题。我的解决方案:
计数器.hpp
class counter
{
static int i;
static nexti()
{
return i++;
}
};
计数器.cpp:
int counter::i = 0;
模板类.hpp
#include "counter.hpp"
template <class T>
tclass
{
static const int id;
};
template <class T>
int tclass<T>::id = counter::nexti();
它可以在 MSVC 和 GCC 中正常工作,但有一个例外是您无法在 switch 语句中使用它。
由于各种原因,我实际上更进一步,并定义了一个预处理器宏,该宏从给定的 name 参数创建一个新类,该参数具有派生自公共基的静态 ID(如上所述(。
这是一个主要基于模板的可能解决方案:
#include<cstddef>
#include<functional>
#include<iostream>
template<typename T>
struct wrapper {
using type = T;
constexpr wrapper(std::size_t N): N{N} {}
const std::size_t N;
};
template<typename... T>
struct identifier: wrapper<T>... {
template<std::size_t... I>
constexpr identifier(std::index_sequence<I...>): wrapper<T>{I}... {}
template<typename U>
constexpr std::size_t get() const { return wrapper<U>::N; }
};
template<typename... T>
constexpr identifier<T...> ID = identifier<T...>{std::make_index_sequence<sizeof...(T)>{}};
// ---
struct A {};
struct B {};
constexpr auto id = ID<A, B>;
int main() {
switch(id.get<B>()) {
case id.get<A>():
std::cout << "A" << std::endl;
break;
case id.get<B>():
std::cout << "B" << std::endl;
break;
}
}
请注意,这需要 C++14。
要将顺序 ID 关联到类型列表,您所要做的就是将该列表提供给模板变量,如上例所示:
constexpr auto id = ID<A, B>;
从那时起,您可以通过 get
方法获取给定类型的给定 id:
id.get<A>()
仅此而已。您可以根据请求在 switch
语句中使用它,如示例代码所示。
请注意,只要将类型追加到与数字 ID 关联的类列表中,标识符在每次编译后和每次执行期间都是相同的。
如果要从列表中删除某个类型,仍可以使用假类型作为占位符,例如:
template<typename> struct noLonger { };
constexpr auto id = ID<noLonger<A>, B>;
这将确保A
不再具有关联的 ID,并且提供给B
的 ID 不会更改。
如果你不会绝对删除A
,你可以使用类似的东西:
constexpr auto id = ID<noLonger<void>, B>;
或者别的什么。
好的.....所以这是我从这个网站上找到的黑客。 它应该有效。 您唯一需要做的就是向struct
添加另一个模板参数,该参数采用计数器"元对象"。 请注意,带有 int
、bool
和 char
的A
都有唯一的 ID,但不能保证int
会被1
,bool
会被2
等,因为模板的启动顺序不一定是已知的。
另一个注意事项:
这不适用于Microsoft视觉C++
#include <iostream>
#include "meta_counter.hpp"
template<typename T, typename counter>
struct A
{
static const size_t ID = counter::next();
};
int main () {
typedef atch::meta_counter<void> counter;
typedef A<int,counter> AInt;
typedef A<char,counter> AChar;
typedef A<bool,counter> ABool;
switch (ABool::ID)
{
case AInt::ID:
std::cout << "Intn";
break;
case ABool::ID:
std::cout << "Booln";
break;
case AChar::ID:
std::cout << "Charn";
break;
}
std::cout << AInt::ID << std::endl;
std::cout << AChar::ID << std::endl;
std::cout << ABool::ID << std::endl;
std::cout << AInt::ID << std::endl;
while (1) {}
}
这是meta_counter.hpp
:
// author: Filip Roséen <filip.roseen@gmail.com>
// source: http://b.atch.se/posts/constexpr-meta-container
#ifndef ATCH_META_COUNTER_HPP
#define ATCH_META_COUNTER_HPP
#include <cstddef>
namespace atch { namespace {
template<class Tag>
struct meta_counter {
using size_type = std::size_t;
template<size_type N>
struct ident {
friend constexpr size_type adl_lookup (ident<N>);
static constexpr size_type value = N;
};
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
template<class Ident>
struct writer {
friend constexpr size_type adl_lookup (Ident) {
return Ident::value;
}
static constexpr size_type value = Ident::value;
};
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
template<size_type N, int = adl_lookup (ident<N> {})>
static constexpr size_type value_reader (int, ident<N>) {
return N;
}
template<size_type N>
static constexpr size_type value_reader (float, ident<N>, size_type R = value_reader (0, ident<N-1> ())) {
return R;
}
static constexpr size_type value_reader (float, ident<0>) {
return 0;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
template<size_type Max = 64>
static constexpr size_type value (size_type R = value_reader (0, ident<Max> {})) {
return R;
}
template<size_type N = 1, class H = meta_counter>
static constexpr size_type next (size_type R = writer<ident<N + H::value ()>>::value) {
return R;
}
};
}}
#endif /* include guard */
使用模板,如果 constexpr,则需要 C++17
#include <iostream>
template <typename Type, typename... Types>
struct TypeRegister{
template<typename Queried_type>
static constexpr int id(){
if constexpr (std::is_same_v<Type, Queried_type>) return 0;
else{
static_assert((sizeof...(Types) > 0), "You shan't query a type you didn't register first");
return 1 + TypeRegister<Types...>::template id<Queried_type>();
}
}
};
int main(){
using reg_map = TypeRegister<int, float, char, const int&>;
std::cout << reg_map::id<const int&>() << std::endl;// 3
// std::cout << reg_map::id<const int>() << std::endl;// error
}
这是不可能的。静态对象的地址是最接近唯一 id 的地址,但是为了获取此类对象的地址(甚至是静态常量积分(,必须在某处定义它们。根据一个定义规则,它们应该在 CPP 文件中定义,这无法完成,因为它们是模板。如果您在头文件中定义静态,那么每个编译单元将在不同的地址上实现自己的版本。
前我遇到了类似的问题。我一直在寻找一种技术来定义每次执行时相同的标识符。
如果这是一个要求,这里有另一个问题,或多或少地探讨了相同的问题(当然,它伴随着它的好答案(。
无论如何,我没有使用建议的解决方案。它遵循对我当时所做的事情的描述。
您可以定义如下所示的constexpr
函数:
static constexpr uint32_t offset = 2166136261u;
static constexpr uint32_t prime = 16777619u;
constexpr uint32_t fnv(uint32_t partial, const char *str) {
return str[0] == 0 ? partial : fnv((partial^str[0])*prime, str+1);
}
inline uint32_t fnv(const char *str) {
return fnv(offset, str);
}
然后是这样的类,从中继承:
template<typename T>
struct B {
static const uint32_t id() {
static uint32_t val = fnv(T::identifier);
return val;
}
};
CRTP成语完成其余的工作。
例如,可以按如下方式定义派生类:
struct C: B<C> {
static const char * identifier;
};
const char * C::identifier = "ID(C)";
只要为不同的类提供不同的标识符,就会有可用于区分类型的唯一数值。
标识符不需要是派生类的一部分。例如,您可以通过特征提供它们:
template<typename> struct trait;
template<> struct trait { static const char * identifier; };
// so on with all the identifiers
template<typename T>
struct B {
static const uint32_t id() {
static uint32_t val = fnv(trait<T>::identifier);
return val;
}
};
优势:
- 易于实施。
- 没有依赖关系。
- 每次执行期间的数值都相同。
- 如果需要,类可以共享相同的数字标识符。
弊:
- 容易出错:复制和粘贴很快就会成为你最大的敌人。
它遵循上述内容的最小工作示例。
我调整了代码,以便能够在switch
语句中使用 ID
成员方法:
#include<type_traits>
#include<cstdint>
#include<cstddef>
static constexpr uint32_t offset = 2166136261u;
static constexpr uint32_t prime = 16777619u;
template<std::size_t I, std::size_t N>
constexpr
std::enable_if_t<(I == N), uint32_t>
fnv(uint32_t partial, const char (&)[N]) {
return partial;
}
template<std::size_t I, std::size_t N>
constexpr
std::enable_if_t<(I < N), uint32_t>
fnv(uint32_t partial, const char (&str)[N]) {
return fnv<I+1>((partial^str[I])*prime, str);
}
template<std::size_t N>
constexpr inline uint32_t fnv(const char (&str)[N]) {
return fnv<0>(offset, str);
}
template<typename T>
struct A {
static constexpr uint32_t ID() {
return fnv(T::identifier);
}
};
struct C: A<C> {
static constexpr char identifier[] = "foo";
};
struct D: A<D> {
static constexpr char identifier[] = "bar";
};
int main() {
constexpr auto val = C::ID();
switch(val) {
case C::ID():
break;
case D::ID():
break;
default:
break;
}
}
请注意,如果要在非常量表达式中使用ID
,则必须在某处定义identifier
s,如下所示:
constexpr char C::identifier[];
constexpr char D::identifier[];
一旦你这样做了,你可以做这样的事情:
int main() {
constexpr auto val = C::ID();
// Now, it is well-formed
auto ident = C::ID();
// ...
}
这是一个C++代码,它使用 __DATE__
和 __TIME__
宏来获取类型的唯一标识符<T>
格式:
// __DATE__ "??? ?? ????"
// __TIME__ "??:??:??"
这是一个质量差的哈希函数:
#define HASH_A 8416451
#define HASH_B 11368711
#define HASH_SEED 9796691
+ __DATE__[0x0] * 389
+ __DATE__[0x1] * 82421
+ __DATE__[0x2] * 1003141
+ __DATE__[0x4] * 1463339
+ __DATE__[0x5] * 2883371
+ __DATE__[0x7] * 4708387
+ __DATE__[0x8] * 4709213
+ __DATE__[0x9] * 6500209
+ __DATE__[0xA] * 6500231
+ __TIME__[0x0] * 7071997
+ __TIME__[0x1] * 10221293
+ __TIME__[0x3] * 10716197
+ __TIME__[0x4] * 10913537
+ __TIME__[0x6] * 14346811
+ __TIME__[0x7] * 15485863
unsigned HASH_STATE = HASH_SEED;
unsigned HASH() {
return HASH_STATE = HASH_STATE * HASH_A % HASH_B;
}
使用哈希函数:
template <typename T>
class A
{
public:
static const unsigned int ID;
};
template <>
const unsigned int A<float>::ID = HASH();
template <>
const unsigned int A<double>::ID = HASH();
template <>
const unsigned int A<int>::ID = HASH();
template <>
const unsigned int A<short>::ID = HASH();
#include <iostream>
int main() {
std::cout << A<float>::ID << std::endl;
std::cout << A<double>::ID << std::endl;
std::cout << A<int>::ID << std::endl;
std::cout << A<short>::ID << std::endl;
}
如果非单调值和intptr_t
是可以接受的:
template<typename T>
struct TypeID
{
private:
static char id_ref;
public:
static const intptr_t ID;
};
template<typename T>
char TypeID<T>::id_ref;
template<typename T>
const intptr_t TypeID<T>::ID = (intptr_t)&TypeID<T>::id_ref;
如果你必须有整数,或者必须有单调递增的值,我认为使用静态构造函数是唯一的方法:
// put this in a namespace
extern int counter;
template<typename T>
class Counter {
private:
Counter() {
ID_val = counter++;
}
static Counter init;
static int ID_val;
public:
static const int &ID;
};
template<typename T>
Counter<T> Counter<T>::init;
template<typename T>
int Counter<T>::ID_val;
template<typename T>
const int &Counter<T>::ID = Counter<T>::ID_val;
// in a non-header file somewhere
int counter;
请注意,如果在共享库和应用程序之间共享这些技术,则这两种技术都不安全!
>另一种选择是考虑以下具有唯一静态成员字段type
的类Data
:
template <class T>
class Data
{
public:
static const std::type_index type;
};
// do [static data member initialization](http://stackoverflow.com/q/11300652/3041008)
// by [generating unique type id](http://stackoverflow.com/q/26794944/3041008)
template <class T>
std::type_index const Data<T>::type = std::type_index(typeid(T));
产生输出 ( MinGWx64-gcc4.8.4 -std=c++11 -O2
(
printf("%s %sn", Data<int>::type.name(), Data<float>::type.name())
//prints "i f"
它不完全是一个整数 id 或漂亮的可打印字符串,也不是一个constexpr
,但可以用作(无(有序关联容器中的索引。
如果Data.h
标头包含在多个文件中(相同的hashCode()
值(,它似乎也可以工作。
实用的解决方案,如果您愿意为要使用的每个type
编写一个额外的行DECLARE_ID(type)
:
#include <iostream>
template<class> struct my_id_helper;
#define DECLARE_ID(C) template<> struct my_id_helper<C> { enum {value = __COUNTER__ }; }
// actually declare ids:
DECLARE_ID(int);
DECLARE_ID(double);
// this would result in a compile error: redefinition of struct my_id_helper<int>’
// DECLARE_ID(int);
template<class T>
class A
{
public:
static const unsigned int ID = my_id_helper<T>::value;
};
int main()
{
switch(A<int>::ID)
{
case A<int>::ID: std::cout << "it's an int!n"; break;
case A<double>::ID: std::cout << "it's a double!n"; break;
// case A<float>::ID: // error: incomplete type ‘my_id_helper<float>’
default: std::cout << "it's something elsen"; break;
}
}
template<typename T>
static void get_type_id() { void* x; new (x) T(); }
using type_id_t = void(*)();
通过优化工作正常