编译时常量id

鉴于以下情况:

template<typename T> class A { public: static const unsigned int ID = ?; }; 

我希望ID为每个T生成一个唯一的编译时间ID。我已经考虑过了__COUNTER__和boost PP库,但到目前为止还没有成功。 我怎样才能做到这一点?

编辑:ID必须可以在switch语句中使用

编辑2:所有基于静态方法或成员的地址的答案是不正确的。 尽pipe它们确实创build了一个唯一的ID,但它们在编译时不能parsing,因此不能用作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 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))等等不同。

我最近遇到了这个确切的问题。 我的解决scheme

counter.hpp

 class counter { static int i; static nexti() { return i++; } }; 

Counter.cpp:

 int counter::i = 0; 

templateclass.hpp

 #include "counter.hpp" template <class T> tclass { static const int id; }; template <class T> int tclass<T>::id = counter::nexti(); 

它似乎在MSVC和GCC中正常工作,除了在switch语句中不能使用它的一个例外。

出于各种原因,我实际上走得更远了,并且定义了一个预处理器macros,它从一个给定的名称参数中创build一个新的类,该静态ID(如上所示)来自一个公共基础。

使用此答案中的代码可以从string中生成编译时HASH。

如果您可以修改模板以包含一个额外的整数并使用macros来声明该variables:

 template<typename T, int ID> struct A { static const int id = ID; }; #define DECLARE_A(x) A<x, COMPILE_TIME_CRC32_STR(#x)> 

使用这个macros作为types声明,id成员包含一个types名称的散列。 例如:

 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; } 

由于types名称被转换为string,所以对types名称文本的任何修改都会生成一个不同的ID。 例如:

 static_assert(DECLARE_A(size_t)::id != DECLARE_A(std::size_t)::id, ""); 

另一个缺点是由于发生散列冲突的可能性。

我通常使用的是这样的:

 template<typename> void type_id(){} using type_id_t = void(*)(); 

由于函数的每个实例都有自己的地址,所以可以使用该地址来标识types:

 // 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! } 

这是不能做到的。 一个静态对象的地址是最接近你可以得到一个唯一的ID,但是为了获取这样的对象(甚至静态const积分)的地址,他们必须在某个地方定义。 根据一个定义规则,它们应该在CPP文件中定义,由于它们是模板,所以无法完成。 如果你在一个头文件中定义静态,那么每个编译单元将获得它自己的版本,当然在不同的地址实现它。

这是一个可能的解决scheme,主要基于模板:

 #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。

您只需将顺序标识符关联到types列表就可以将该列表提供给模板variables,如上例所示:

 constexpr auto id = ID<A, B>; 

从这一点开始,您可以通过get方法get给定types的给定ID:

 id.get<A>() 

就这样。 您可以按照要求在switch语句中使用它,如示例代码中所示。

请注意,只要将types附加到与数字标识相关联的类的列表中,标识在每次编译之后和每次执行时都是相同的。
如果要从列表中删除某个types,则仍然可以使用types作为占位符,例如:

 template<typename> struct noLonger { }; constexpr auto id = ID<noLonger<A>, B>; 

这将确保A不再有关联的ID,并且给予B ID不会改变。
如果你不肯定删除A ,你可以使用如下的东西:

 constexpr auto id = ID<noLonger<void>, B>; 

pipe他呢。

好的…..所以这是一个黑客,我从这个网站find。 它应该工作。 你需要做的唯一的事情就是将另一个模板参数添加到你的struct ,采取一个计数器“元对象”。 请注意,与intboolchar都具有唯一的ID,但不能保证int将是1bool将是2等,因为模板的发起顺序不一定是已知的。

另一个说明:

这不适用于Microsoft Visual 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 << "Int\n"; break; case ABool::ID: std::cout << "Bool\n"; break; case AChar::ID: std::cout << "Char\n"; 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 */ 

用这个常量expression式计数器:

 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; } 

输出:(GCC,C ++ 14)

 1 2 3 3 

缺点是您需要猜测常量expression式计数器的派生类数量的上限。

如果非单调值和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; 

请注意,如果您在共享库和应用程序之间共享这些技术,这两种技术都是安全的!

另一种select是使用唯一的静态成员字段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 %s\n", Data<int>::type.name(), Data<float>::type.name()) //prints "if" 

它不完全是一个整数id或可打印的string,也不是一个constexpr ,但可以用作(un)有序关联容器的索引 。
如果Data.h头包含在多个文件中( hashCode()值相同hashCode()它也可以工作。

几个月前我有类似的问题。 我正在寻找一种技术来定义每个执行过程中相同的标识符。
如果这是一个要求, 这是另一个问题,探讨或多或less相同的问题(当然,它带来了很好的答案)。
无论如何,我没有使用build议的解决scheme。 下面是我当时做了什么的描述。


你可以像下面这样定义一个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); } 

然后像这样的类从中inheritance:

 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)"; 

只要您为不同的类提供不同的标识符,您将拥有唯一的数字值,可用于区分这些types。

标识符不需要成为派生类的一部分。 作为一个例子,你可以通过一个特性来提供它们:

 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; } } 

请注意,如果要在非常量expression式中使用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__macros来获取types<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; } 

这里是一个实用的解决scheme,如果你可以为你想要使用的每种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 else\n"; break; } }