You need some way to get from the raw bytes in input
to a type.
The index template parameter to std::variant_alternative
must be a compile time constant and the MID
bytes inside input
are not compile time constants - so you can't use that.
MyVariant decode(const std::vector<uint8_t>& input) {
if(input.size() < 4) throw std::runtime_error("nope");
// extract `mid` (use whatever way you use already):
uint32_t mid = (uint32_t(input[0]) << 24) +
(uint32_t(input[1]) << 16) +
(uint32_t(input[2]) << 8) + input[3];
switch(mid) { // lookup the right function to use for decoding
case S3::MID: return decode<S3>(input);
case S5::MID: return decode<S5>(input);
default:
MyVariant rv;
//generic decoding
return rv;
}
}
Where the decode
function template and specializations could be:
template<class S> MyVariant decode(const std::vector<uint8_t>&);
template <>
MyVariant decode<S3>(const std::vector<uint8_t>& input) {
S3 rv; // special decoding for S3
// ...
return rv;
}
template <>
MyVariant decode<S5>(const std::vector<uint8_t>& input) {
S5 rv; // special decoding for S5
// ...
return rv;
}
A second option could be to put the decoding into the constructor of the actual types:
struct generic_decoder {
generic_decoder(const std::vector<uint8_t>& input) {}
};
struct S1 : generic_decoder { constexpr static uint32_t MID = 1; };
struct S2 : generic_decoder { constexpr static uint32_t MID = 2; };
struct S3 {
constexpr static uint32_t MID = 3;
S3(const std::vector<uint8_t>& input) {
// special decoding for S3
}
};
struct S4 : generic_decoder { constexpr static uint32_t MID = 4; };
struct S5 {
constexpr static uint32_t MID = 5;
S5(const std::vector<uint8_t>& input) {
// special decoding for S5
}
};
This would let the lookup and creation part be pretty clean:
MyVariant decode(const std::vector<uint8_t>& input) {
if (input.size() < 4) throw std::runtime_error("nope");
uint32_t mid = (uint32_t(input[0]) << 24) + (uint32_t(input[1]) << 16) +
(uint32_t(input[2]) << 8) + input[3];
switch(mid) {
case S1::MID: return S1(input);
case S2::MID: return S2(input);
case S3::MID: return S3(input);
case S4::MID: return S4(input);
case S5::MID: return S5(input);
default: throw std::runtime_error("invalid MID in input");
}
}
A third, more expensive, version could be to first create the MyVariant
instance and then use std::visit
to decode
. It works, but it's unnecessary since you already know the type you put in the variant
. Anyway, it could look like this:
struct generic_decoder { void decode(const std::vector<uint8_t>& input) {} };
struct S1 : generic_decoder { constexpr static uint32_t MID = 1; };
struct S2 : generic_decoder { constexpr static uint32_t MID = 2; };
struct S3 {
constexpr static uint32_t MID = 3;
void decode(const std::vector<uint8_t>& input) {
// special decoding for S3
}
};
struct S4 : generic_decoder { constexpr static uint32_t MID = 4; };
struct S5 {
constexpr static uint32_t MID = 5;
void decode(const std::vector<uint8_t>& input) {
// special decoding for S5
}
};
using MyVariant = std::variant<S1, S2, S3, S4, S5>;
And create an array of MyVariant
with default constructed in instances of your types. You then copy the correct variant by indexing and then std::visit
to decode
:
MyVariant decode(const std::vector<uint8_t>& input) {
static const std::array<MyVariant, 5> vs{S1{}, S2{}, S3{}, S4{}, S5{}};
if (input.size() < 4) throw std::runtime_error("nope");
uint32_t mid = (uint32_t(input[0]) << 24) + (uint32_t(input[1]) << 16) +
(uint32_t(input[2]) << 8) + input[3];
auto rv = vs.at(mid - 1); // throws if `mid` is invalid
std::visit([&](auto&& var) { var.decode(input); }, rv);
return rv;
}