Dago
Dago

Reputation: 1389

How to "whitelist" template specializations?

I am trying to create a type safe abstraction for registers. Shortly explained a register can contain a field of bits which have some special meaning and the bits in question can usually be either set to a range of valid values (specified by an enum) or to any integer value (if so specified). The fields in a single register are described by an enum.

What I am having some trouble with is how do I create a template that allows me to specify the valid combinations that this enum VALUE can only be paired with a) value specified by an enum b) any integer value. The actual set function is the same for any combination (it will cast down to underlying value from the enum class values). Other thing to note is that I'd like the set() method to be generic in a way that it is in a separate file from the actual register definitions so adding static_asserts() there etc. to prevent some type combinations etc. is not a good solution. I have a solution that is very close to what I want but I'm not sure how can I get the template type deduction to work so that I don't need to type the name of the enum type twice per call.

enum class Definitions : uint32_t
{
    kBit0       = 1UL << 0,
    kBit1       = 1UL << 1,
    kField0     = 0xF0,
    kField1     = 0xF00,
    kField2     = 0xF000
};

enum class Field0Values
{
    Setting1    = 0x01,
    Setting2    = 0x02,
};
enum class Field1Values
{
    Setting1    = 0x01,
    Setting2    = 0x02,
};

template<class MaskType, MaskType value> struct allowed_type;
template<> struct allowed_type<Definitions, Definitions::kField0> {
    using type = Field0Values;
};
template<> struct allowed_type<Definitions, Definitions::kField1> {
    using type = Field1Values;
};
template<> struct allowed_type<Definitions, Definitions::kField2> {
    using type = uint32_t;
};

template<class MaskType, MaskType maskValue>
constexpr void set(typename allowed_type<MaskType, maskValue>::type value) {}

int main()
{
    // Valid set operations
    set<Definitions, Definitions::kField0>(Field0Values::Setting1);
    set<Definitions, Definitions::kField1>(Field1Values::Setting1);
    set<Definitions, Definitions::kField2>(42);
    // Mistakes, will not compile
    set<Definitions, Definitions::kField0>(Field1Values::Setting1);
    set<Definitions, Definitions::kField1>(Field0Values::Setting1);
    set<Definitions, Definitions::kField1>(7);
}

This is nearly what I want except I would like to be able to just say set<Definitions::kField0>(Field0Values::Setting1). I CAN do that if I declare a template specialization like:

template<Definitions mask>
constexpr void set(typename allowed_type<Definitions, mask>::type value);

But the issue with that is that I do not want to provide the implementation for that specialization, I just want it to use the base template function, not sure how can I do that. How can I get the template type deduction to deduce the type without having to create the template specialization?

Also interested if someone has another approach that doesn't have this type deduction issue.

Upvotes: 0

Views: 97

Answers (1)

bitmask
bitmask

Reputation: 34628

How about setting the template argument to auto in both allowed_type and set?

template<auto value> struct allowed_type;

template<> struct allowed_type<Definitions::kField0> {
    using type = Field0Values;
};

template<auto maskValue>
constexpr void set(typename allowed_type<maskValue>::type) {}

Alternatively, if you prefer to keep allowed_type as is, you can also only change set:

template<auto maskValue>
constexpr void set(typename allowed_type<decltype(maskValue), maskValue>::type value) {}

Upvotes: 3

Related Questions