48

鉴于以下情况:

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

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

编辑:ID 必须可用作 switch 语句中的情况

Edit2:所有基于静态方法或成员地址的答案都不正确。尽管它们确实创建了一个唯一 ID,但它们在编译时不会被解析,因此不能用作 switch 语句的情况。

4

17 回答 17

11

假设符合标准的编译器(关于单一定义规则),这就足够了:

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 的定义不满足这些要求,那么行为是未定义的。

于 2011-09-26T23:37:58.497 回答
9

我通常使用的是这样的:

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!
}
于 2016-09-22T13:51:01.450 回答
4

这对我来说似乎没问题:

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;

}
于 2011-09-27T00:10:17.977 回答
4

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

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

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

另一个缺点是由于可能发生哈希冲突。

于 2016-09-16T23:57:15.863 回答
3

使用静态函数的内存地址。

template<typename T>
class A  {
public:
    static void ID() {}
}; 

(&(A<int>::ID))会有所不同(&(A<char>::ID))等等。

于 2011-09-26T22:37:18.667 回答
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 语句中使用它。

由于各种原因,我实际上更进一步,并定义了一个预处理器宏,该宏从给定名称参数创建一个新类,该类具有从公共基础派生的静态 ID(如上)。

于 2011-09-27T12:45:03.183 回答
3

使用这个常量表达式计数器:

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

缺点是您需要猜测派生类数量的上限,常量表达式计数器才能工作。

于 2016-11-30T05:08:19.633 回答
2

好的.....所以这是我从这个网站上找到的一个黑客。它应该工作。您唯一需要做的就是向您添加另一个模板参数,struct该参数采用计数器“元对象”。注意Awith int, boolandchar都有唯一的 ID,但不保证int's 会是会是1等等,因为模板启动的顺序不一定是已知的。 bool2

另一个注意事项:

这不适用于 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 */
于 2016-09-23T22:42:11.063 回答
2

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

#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 并且给定的 idB不会改变。
如果你不想明确删除A,你可以使用类似的东西:

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

管他呢。

于 2016-09-22T20:39:26.900 回答
2

使用模板,如果是 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
}
于 2021-04-06T07:54:50.520 回答
1

这是做不到的。静态对象的地址是您可以获得的最接近唯一 ID 的地址,但是为了获取此类对象的地址(甚至是静态 const 积分),它们必须在某处定义。根据一个定义规则,它们应该在 CPP 文件中定义,因为它们是模板,所以不能这样做。如果您在头文件中定义静态,那么每个编译单元都会在不同的地址上实现它自己的版本。

于 2011-09-26T23:58:44.103 回答
1

几个月前我遇到了类似的问题。我一直在寻找一种技术来定义每次执行时都相同的标识符。
如果这是一个要求,那么这里是另一个探索或多或少相同问题的问题(当然,它带有很好的答案)。
无论如何,我没有使用建议的解决方案。它遵循了我当时所做的事情的描述。


您可以定义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)";

只要你为不同的类提供不同的标识符,你就会有唯一的数值,可以用来区分类型。

标识符不需要是派生类的一部分。例如,您可以通过 trait 提供它们:

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

优点:

  • 易于实施。
  • 没有依赖关系。
  • 每次执行期间数值都相同。
  • 如果需要,类可以共享相同的数字标识符。

缺点:

  • 容易出错:复制粘贴很快就会成为你最大的敌人。

它遵循上面描述的一个最小的工作示例。
我修改了代码,以便能够在语句中使用ID成员方法:switch

#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在非常量表达式中使用,您必须在某处定义identifiers,如下所示:

constexpr char C::identifier[];
constexpr char D::identifier[];

完成后,您可以执行以下操作:

int main() {
    constexpr auto val = C::ID();
    // Now, it is well-formed
    auto ident = C::ID();

    // ...
}
于 2016-09-17T23:03:10.400 回答
1

这是一个 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;
}
于 2016-09-22T12:06:55.973 回答
0

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

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

于 2011-09-26T23:53:36.193 回答
0
template<typename T>
static void get_type_id() { void* x; new (x) T(); }
using type_id_t = void(*)();

优化工作正常

于 2018-10-27T15:31:02.430 回答
0

这是一个实用的解决方案,如果您可以为要使用DECLARE_ID(type)的每个附加行编写一个附加行: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;
     }
 }
于 2016-09-23T17:38:13.377 回答
0

另一种选择是考虑以下Data具有唯一静态成员字段的类type

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 "i f"

它不完全是整数 id 或漂亮的可打印字符串,也不是 a constexpr,但可以用作(无)有序关联容器中的索引。如果标头包含在多个文件中(相同的值)
,它似乎也可以工作。Data.hhashCode()

于 2015-11-01T01:15:33.080 回答