Home > database >  How can I create different types from uint32_t that are statically different?
How can I create different types from uint32_t that are statically different?

Time:11-27

I want to create different types that are uint32_t but are different from compilers perspective -- they can only be compared and assigned to a value of the exact same type. Here is a sample code that I want to achieve:

TextureResourceId t1 = 1000, t2 = 2000;
PipelineResourceId p1 = 1000, p2 = 2000;
BufferResourceId b1 = 1000, b2 = 1000;

if (t1 == t2) // OK!
if (t1 == p1) // Compiler error!
if (t1 == b1) // Compiler error!

I know that I can do some preprocessor magic to achieve this:

#define CREATE_RESOURCE_ID(NAME) \
    class NAME { \
    public: \
        NAME(uint32_t value_): value(value_) {} \
        bool operator==(const NAME &rhs) { return value == rhs.value; } \
    private: \
        uint32_t value; \
    };

CREATE_RESOURCE_ID(TextureResourceId);
CREATE_RESOURCE_ID(BufferResourceId);
CREATE_RESOURCE_ID(PipelineResourceId);

int main() {
    TextureResourceId tex1(1000), tex2(2000);
    BufferResourceId buf1(1000);
    PipelineResourceId pip1(1000);


    if (tex1 == tex2) {}
    if (buf1 == tex1) {}
    if (tex1 == pip1) {}

    return 0;
}

But I would like to know if there is a more C 'y way of doing this (e.g with inheritance or some kind of syntax with enum classes)

CodePudding user response:

You can create a templated archetype and make type-tagged versions of that.

I made this a while back and haven't used it yet, so no testing, but I think the idea should be sound:

template<typename Type, typename Number>
class typed_id
{
public:
    explicit typed_id(Number n): n(n) {}
    typed_id(typed_id const& id): n(id.n) {}

    typed_id& operator=(typed_id const& n) { this->n  = n.n; return *this; }

    bool operator<(typed_id id) const { return this->n < id.n; }
    bool operator>(typed_id id) const { return this->n > id.n; }

    bool operator==(typed_id id) const { return this->n == id.n; }
    bool operator!=(typed_id id) const { return this->n != id.n; }

private:
    Number n;
};

// tag types to differentiate each
// instantiation of the archetype
struct TextureResourceIdTag{};
struct PipelineResourceIdTag{};
struct BufferResourceIdTag{};

// actual types    
using TextureResourceId = typed_id<TextureResourceIdTag, std::size_t>;
using PipelineResourceId = typed_id<PipelineResourceIdTag, std::size_t>;
using BufferResourceId = typed_id<BufferResourceIdTag, std::size_t>;

int main()
{
    TextureResourceId t1{1000};
    TextureResourceId t2{2000};
    PipelineResourceId p1{1000};
    PipelineResourceId p2{2000};
    BufferResourceId b1{1000};
    BufferResourceId b2{2000};

    if (t1 == t2) {} // OK!
    if (t1 == p1) {} // Compiler error!
    if (t1 == b1) {} // Compiler error!
}

CodePudding user response:

Since you only seem to need to check identity, an enum should be the natural choice and as you mention, enum class can be used to ensure the underlying type is uint32_t:

enum class TextureResourceId: uint32_t
{
    id1 = 1000,
    id2 = 2000
};

enum class PipelineResource: uint32_t
{
    id1 = 1000,
    id2 = 2000
};

enum class BufferResourceId: uint32_t
{
    id1 = 1000,
    id2 = 2000
};

With values initialized as in the example:

TextureResourceId t1 = TextureResourceId::id1, t2 = TextureResourceId::id2;
PipelineResourceId p1 = PipelineResourceId::id1, p2 = PipelineResourceId::id2;
BufferResourceId b1 = BufferResourceId::id1, b2 = BufferResourceId::id2;

The compiler should behave as specified, i.e. allow IDs to be compared within the same class, but not across different classes.

Note: if you do not want to explicitly declare all possible identities in the enum classes, identities can also be constructed from integers, e.g. TextureResourceId(1000).

  •  Tags:  
  • c
  • Related