编译时常量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
,采取一个计数器“元对象”。 请注意,与int
, bool
和char
都具有唯一的ID,但不能保证int
将是1
和bool
将是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; } }