Compile-time constant id

前端 未结 16 2209
不知归路
不知归路 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: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
    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 {
        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 struct trait;
    template<> struct trait { static const char * identifier; };
    
    // so on with all the identifiers
    
    template
    struct B {
        static const uint32_t id() {
            static uint32_t val = fnv(trait::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
    #include
    #include
    
    static constexpr uint32_t offset = 2166136261u;
    static constexpr uint32_t prime = 16777619u;
    
    template
    constexpr
    std::enable_if_t<(I == N), uint32_t>
    fnv(uint32_t partial, const char (&)[N]) {
        return partial;
    }
    
    template
    constexpr
    std::enable_if_t<(I < N), uint32_t>
    fnv(uint32_t partial, const char (&str)[N]) {
        return fnv((partial^str[I])*prime, str);
    }
    
    template
    constexpr inline uint32_t fnv(const char (&str)[N]) {
        return fnv<0>(offset, str);
    }
    
    template
    struct A {
        static constexpr uint32_t ID() {
            return fnv(T::identifier);
        }
    };
    
    struct C: A {
        static constexpr char identifier[] = "foo";
    };
    
    struct D: A {
        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();
    
        // ...
    }
    

提交回复
热议问题