Compile-time constant id

前端 未结 16 2136
不知归路
不知归路 2020-12-02 20:06

Given the following:

template
class A
{
public:
    static const unsigned int ID = ?;
};

I want ID to generate a unique c

相关标签:
16条回答
  • 2020-12-02 20:21

    It is possible to generate a compile time HASH from a string using the code from this answer.

    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. 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. 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.

    0 讨论(0)
  • 2020-12-02 20:21

    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.

    0 讨论(0)
  • 2020-12-02 20:22

    Here is a C++ code that uses __DATE__ and __TIME__ macro to get unique identifiers for types <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;
    }
    
    0 讨论(0)
  • 2020-12-02 20:23

    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". 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.

    Another note:

    This will not work with 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:

    // 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 */
    
    0 讨论(0)
  • 2020-12-02 20:25

    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:

    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.
    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:

    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:

    #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 identifiers as it follows:

    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();
    
        // ...
    }
    
    0 讨论(0)
  • 2020-12-02 20:28

    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:

     #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;
         }
     }
    
    0 讨论(0)
提交回复
热议问题