Reputation: 2014
For a binary protocol library I am developing I make wide use of StrongType<T>
, and I'm extending this concept to Enums.
I have a InBitStream
object that implements reading from a byte array on a bit-by-bit base, I mean, each field can be read from the array in a fixed and predefined number of bits.
For example, I can read a StrongType
d field that is stored in 5 bits, followed by a 3 other bits in this way:
class SomePacketType {
void readFromStream(DataPacket const &p) {
InBitStream stream(p);
auto field1 = makeBitField<5>(mFirstField); // references to mFirstField, using 5 bits
auto field2 = makeBitField<3>(mSecondField); // same, 3 bits
stream >> field1 >> field2;
}
of course, somewhere it is defined this operator
class InBitStream {
template<int N, typename T, typename TAG>
friend InBitStream &operator >> (InBitStream &stream, BitField<N,StrongType<T,TAG>> &value) { ...
}
}
The same is done for Enums. Enums defined this way:
struct XTag {
};
enum class XEnum {
N = 0, A = 1, B = 2, C = 3, D = 4,
};
using X = utils::BitAwareEnum<3, XEnum, XTag>;
My idea is now to add a validation function, to check that an enum (or a strong type) is read from the stream and validated according to some special value. For example X
should not receive a value > 4.
My intention is to use it this way, supposing there's some ValidatedField
concept:
template <???>
InStream &operator >> (InStream &stream, ValidatedField<???> &v) {
v.validate();
return stream >> static_cast<some_base_class?>(v);
}
How should ValidatedField be defined? Note that the validation function depends on the templated type. For example for StrongType it would check that it is positive, while for enums it must check against max, min and some internal invalid values.
It is required that the ValidatedField have the same interface as type, so I cannot compose it, and no virtual
functions are allowed, because the types are never used polymorphically.
(The pattern should be something like a decorator, perhaps).
Any Idea? Thanks.
Upvotes: 2
Views: 84
Reputation: 2014
I was able to find a good solution then.
This is the ValidatedField
class.
template<typename Type, typename Validator>
struct ValidatedType : Type {
friend InBitStream &operator>>(InBitStream &stream, ValidatedType<Type, Validator> &v)
{
stream >> static_cast<Type &>(v);
Validator::validate(v);
return stream;
}
};
This simply create a Tag for the Validated type. The stream operator >>
is dispatched using it. The Type
template type is used to expose the embedded type, and the Validator
type is used like a functor
. The Validator must expose a static function validate(Type &const)
.
In the stream operator implementation, the v
argument must be upcast to Type &
, to correctly call the same operation on the "decorated" type.
This is how it is used:
struct XTag {
};
enum class XEnum {
A = 1, B = 2, C = 3, D = 4,
};
using XE = utils::BitAwareEnum<4, XEnum, XTag>;
struct XValidator {
static void validate(XE const &x)
{
if (x.underlyingValue() <= 0 || x.underlyingValue() > 4) {
throw utils::InBitStream::IllegalValueException("Illegal XE value");
}
}
};
using X = utils::ValidatedType<XE, XValidator>;
X x;
What I like of this solution: - I have a "tree" of decorated types whose functionalities can be "plugged-in" as needed
What I don't like of this solution:
- The structure of the XValidator
is somehow verbose, I'd prefer to have a more readable way to implement it. I need to remember that the validate()
function must be declared static.
Feel free to comment with improvements or alternative solutions.
For those courious, the project is open sourced here: https://gitlab.com/ffuga/gutils
Update:
Indeed as @Incomputable has noted, this is a Policy Based design.
Rearranging the code as in the example in the wikipedia page, I fixed the "static" issue.
This is the pretty definitive code:
template<typename Type, typename Validator>
struct ValidatedType : Type, Validator {
using Validator::validate;
friend InBitStream &operator>>(InBitStream &stream, ValidatedType<Type, Validator> &v)
{
stream >> static_cast<Type &>(v);
v.validate(v);
return stream;
}
};
Usage:
struct XValidator {
void validate(XE const &x)
{
if (x.underlyingValue() <= 0 || x.underlyingValue() > 4) {
throw utils::InBitStream::IllegalValueException("Illegal XE value");
}
}
};
Upvotes: 1