简体   繁体   English

编译时常量 id

[英]Compile-time constant id

Given the following:鉴于以下情况:

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

I want ID to generate a unique compile time ID for every T. I've considered __COUNTER__ and the boost PP library but have been unsuccessful so far.我希望 ID 为每个 T 生成一个唯一的编译时 ID。我考虑过__COUNTER__和 boost PP 库,但到目前为止都没有成功。 How can I achieve this?我怎样才能做到这一点?

Edit: ID has to be usable as the case in a switch statement编辑:ID 必须可用于 switch 语句中的情况

Edit2: All the answers based on the address of a static method or member are incorrect. Edit2:所有基于静态方法或成员地址的答案都不正确。 Although they do create a unique ID they are not resolved in compile time and therefore can not be used as the cases of a switch statement.尽管它们确实创建了唯一的 ID,但它们不会在编译时解析,因此不能用作 switch 语句的情况。

This is sufficient assuming a standards conforming compiler (with respect to the one definition rule):假设编译器符合标准(关于一个定义规则),这就足够了:

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;

From the C++ standard 3.2.5 One definition rule [basic.def.odr] (bold emphasis mine):来自 C++ 标准 3.2.5 One definition rule [basic.def.odr](粗体强调我的):

... If D is a template and is defined in more than one translation unit, then the last four requirements from the list above shall apply to names from the template's enclosing scope used in the template definition (14.6.3), and also to dependent names at the point of instantiation (14.6.2). ... 如果 D 是一个模板并且在多个翻译单元中定义,那么上面列表中的最后四项要求应适用于模板定义(14.6.3)中使用的模板封闭范围的名称,也适用于实例化点的依赖名称 (14.6.2)。 If the definitions of D satisfy all these requirements, then the program shall behave as if there were a single definition of D. If the definitions of D do not satisfy these requirements, then the behavior is undefined.如果 D 的定义满足所有这些要求,那么程序的行为就好像只有一个 D的定义如果 D 的定义不满足这些要求,则该行为是未定义的。

What I usually use is this:我通常使用的是这样的:

template<typename>
void type_id(){}

using type_id_t = void(*)();

Since every instantiation of the function has it's own address, you can use that address to identify 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!
}

It is possible to generate a compile time HASH from a string using the code from this answer .可以使用此答案中的代码从字符串生成编译时 HASH。

If you can modify the template to include one extra integer and use a macro to declare the variable:如果您可以修改模板以包含一个额外的整数并使用宏来声明变量:

template<typename T, int ID> struct A
{
    static const int id = ID;
};

#define DECLARE_A(x) A<x, COMPILE_TIME_CRC32_STR(#x)>

Using this macro for the type declaration, the id member contains a hash of the type name.将此宏用于类型声明,id 成员包含类型名称的散列。 For example:例如:

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

As the type name is converted to a string, any modification to the type name text results on a different id.当类型名称转换为字符串时,对类型名称文本的任何修改都会产生不同的 id。 For example:例如:

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

Another drawback is due to the possibility for a hash collision to occur.另一个缺点是可能发生散列冲突。

This seems to work OK for me:这对我来说似乎工作正常:

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;

}

Use the memory address of a static function.使用静态函数的内存地址。

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

(&(A<int>::ID)) will be different from (&(A<char>::ID)) and so on. (&(A<int>::ID))将不同于(&(A<char>::ID))等等。

I encountered this exact problem recently.我最近遇到了这个确切的问题。 My solution:我的解决方案:

counter.hpp计数器.hpp

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

Counter.cpp:计数器.cpp:

int counter::i = 0;

templateclass.hpp模板类.hpp

#include "counter.hpp"

    template <class T>
    tclass
    {
        static const int id;
    };

    template <class T>
    int tclass<T>::id = counter::nexti();

It appers to work properly in MSVC and GCC, with the one exception that you can't use it in a switch statement.它似乎可以在 MSVC 和 GCC 中正常工作,唯一的例外是您不能在 switch 语句中使用它。

For various reasons I actually went further, and defined a preprocessor macro that creates a new class from a given name parameter with a static ID (as above) that derives from a common base.出于各种原因,我实际上更进一步,并定义了一个预处理器宏,该宏根据给定的名称参数创建一个新类,该类具有源自公共基础的静态 ID(如上所述)。

Here is a possible solution mostly based on templates:这是一个主要基于模板的可能解决方案:

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

Note that this requires C++14.请注意,这需要 C++14。

All you have to do to associate sequential ids to a list of types is to provide that list to a template variable as in the example above:要将顺序 id 与类型列表相关联,您只需将该列表提供给模板变量,如上例所示:

constexpr auto id = ID<A, B>;

From that point on, you can get the given id for the given type by means of the get method:从那时起,您可以通过get方法get给定类型的给定 id:

id.get<A>()

And that's all.就这样。 You can use it in a switch statement as requested and as shown in the example code.您可以根据要求在switch语句中使用它,如示例代码所示。

Note that, as long as types are appended to the list of classes to which associate a numeric id, identifiers are the same after each compilation and during each execution.请注意,只要将类型附加到与数字 id 相关联的类列表中,每次编译后和每次执行期间的标识符都是相同的。
If you want to remove a type from the list, you can still use fake types as placeholders, as an example:如果你想从列表中删除一个类型,你仍然可以使用类型作为占位符,例如:

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

This will ensure that A has no longer an associated id and the one given to B won't change.这将确保A不再有关联的 id 并且给B那个不会改变。
If you won't to definitely delete A , you can use something like:如果您不想绝对删除A ,则可以使用以下内容:

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

Or whatever.管他呢。

Ok.....so this is a hack that I found from this website.好的.....所以这是我从这个网站上发现的一个黑客。 It should work.它应该工作。 The only thing you need to do is add another template parameter to your struct that takes a counter "meta-object".您唯一需要做的就是向struct中添加另一个模板参数,该参数采用计数器“元对象”。 Note that A with int , bool and char all have unique IDs, but it is not guaranteed that int 's will be 1 and bool will be 2 , etc., because the order in which templates are initiated is not necessarily known.请注意,带有intboolchar A都有唯一的 ID,但不能保证int1bool将为2等,因为模板启动的顺序不一定知道。

Another note:另一个注意事项:

This will not work with Microsoft Visual C++这不适用于 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) {}
}

Here is meta_counter.hpp :这是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 */

Using this constant expression counter:使用这个常量表达式计数器:

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

output: (GCC, C++14)输出:(GCC,C++14)

1
2
3
3

The downside is you will need to guess an upper bound on the number of derived classes for the constant expression counter to work.缺点是您需要猜测常量表达式计数器工作的派生类数量的上限。

I had a similar problem a few months ago.几个月前我遇到了类似的问题。 I was looking for a technique to define identifiers that are the same over each execution.我一直在寻找一种技术来定义每次执行时都相同的标识符。
If this is a requirement, here is another question that explores more or less the same issue (of course, it comes along with its nice answer).如果这是一个要求,那么这里有另一个问题或多或少地探讨了相同的问题(当然,它附带了很好的答案)。
Anyway I didn't use the proposed solution.无论如何,我没有使用建议的解决方案。 It follows a description of what I did that time.它遵循了我当时所做的事情的描述。


You can define a constexpr function like the following one:您可以像下面这样定义一个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);
}

Then a class like this from which to inherit:然后是一个像这样继承的类:

template<typename T>
struct B {
    static const uint32_t id() {
        static uint32_t val = fnv(T::identifier);
        return val;
    }
};

CRTP idiom does the rest. CRTP 习惯用法做剩下的。
As an example, you can define a derived class as it follows:例如,您可以按如下方式定义派生类:

struct C: B<C> {
    static const char * identifier;
};

const char * C::identifier = "ID(C)";

As long as you provide different identifiers for different classes, you will have unique numeric values that can be used to distinguish between the types.只要您为不同的类提供不同的标识符,您将拥有可用于区分类型的唯一数值。

Identifiers are not required to be part of the derived classes.标识符不需要是派生类的一部分。 As an example, you can provide them by means of a trait:例如,您可以通过 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;
    }
};

Advantages:优点:

  • Easy to implement.易于实施。
  • No dependencies.没有依赖性。
  • Numeric values are the same during each execution.每次执行期间数值都相同。
  • Classes can share the same numeric identifier if needed.如果需要,类可以共享相同的数字标识符。

Disadvantages:缺点:

  • Error-prone: copy-and-paste can quickly become your worst enemy.容易出错:复制和粘贴很快就会成为你最大的敌人。

It follows a minimal, working example of what has been described above.它遵循上述内容的最小工作示例。
I adapted the code so as to be able to use the ID member method in a switch statement:我修改了代码,以便能够在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;
    }
}

Please, note that if you want to use ID in a non-constant expression, you must define somewhere the identifier s as it follows:请注意,如果您想在非常量表达式中使用ID ,则必须在某处定义identifier s,如下所示:

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

Once you did it, you can do something like this:完成后,您可以执行以下操作:

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

    // ...
}

Here is a C++ code that uses __DATE__ and __TIME__ macro to get unique identifiers for types <T>这是一个 C++ 代码,它使用__DATE____TIME__宏来获取类型<T>唯一标识符

Format:格式:

// __DATE__ "??? ?? ????"
// __TIME__ "??:??:??"

This is a poor quality hash function:这是一个质量很差的哈希函数:

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

Using the hash function:使用哈希函数:

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

using template and if constexpr, need c++17使用模板,如果 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
}

This can't be done.这是做不到的。 An address to a static object is the closest you can get to a unique id, however in order to take addresses of such objects (even static const integrals) they must be defined somewhere.静态对象的地址是最接近唯一 id 的地址,但是为了获取此类对象的地址(甚至是静态常量积分),它们必须在某处定义。 Per the one definition rule, they should be defined within a CPP file, which cannot be done since they are templates.根据一个定义规则,它们应该在 CPP 文件中定义,因为它们是模板,所以不能这样做。 If you define the statics within a header file, then each compilation unit will get its own version of it implemented of course at different addresses.如果您在头文件中定义静态,那么每个编译单元将获得它自己的版本,当然在不同的地址实现。

Another alternative is to consider the following class Data with the unique, static member field type :另一种选择是考虑以下具有唯一静态成员字段typeData

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

produces the output ( MinGWx64-gcc4.8.4 -std=c++11 -O2 )产生输出( MinGWx64-gcc4.8.4 -std=c++11 -O2

printf("%s %s\n", Data<int>::type.name(), Data<float>::type.name())
//prints "i f"

It's not exactly an integer id or pretty-printable string, nor a constexpr , but can be used as an index in (un)ordered associative containers .它不完全是整数 id 或可漂亮打印的字符串,也不是constexpr ,但可以用作(无)有序关联容器中的索引
It also appears to work if the Data.h header is included in multiple files (same hashCode() values).如果Data.h标头包含在多个文件中(相同的hashCode()值),它似乎也有效。

Here is a pragmatic solution, if you are ok with writing a single additional line DECLARE_ID(type) for each type you want to use:这是一个实用的解决方案,如果您可以为要使用的每种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;
     }
 }
template<typename T>
static void get_type_id() { void* x; new (x) T(); }
using type_id_t = void(*)();

works fine with optimizations优化工作正常

If non-monotonous values and an intptr_t are acceptable:如果可以接受非单调值和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;

If you must have ints, or must have monotonically incrementing values, I think using static constructors is the only way to go:如果你必须有整数,或者必须有单调递增的值,我认为使用静态构造函数是唯一的方法:

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

Note that neither of these techniques is safe if you are sharing them between shared libraries and your application!请注意,如果您在共享库和应用程序之间共享这些技术,则它们都不安全!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM