Given a class that is defined to operate on a collection of complex types defined in a parameter pack, I've come up with this pattern to index the types in the parameter pack at runtime:
#include <iostream>
#include <variant>
template <typename T>
std::pair<typename T::typeK, typename T::typeV> pushdata_deserialize_impl(const uint8_t *serialized);
template <typename K, typename V>
class DataClass{
public:
using typeK = K;
using typeV = V;
};
template <class... DataClassCollection>
class DeSerializer{
public:
using VariantType = std::variant<std::monostate, std::pair<typename DataClassCollection::typeK, typename DataClassCollection::typeV>...>;
explicit DeSerializer(){}
VariantType pushdata(const uint8_t *dat_serialized, uint8_t datatype_id){
// deserialize based on the datatype_id, receive a variant of pairs
VariantType dat = pushdata_deserialize_helper(static_cast<int>(datatype_id), dat_serialized);
return dat;
}
// this returns a variant with monostate in it
auto pushdata_deserialize_helper(int index, const uint8_t *serialized) {
// returns a tuple
VariantType result;
size_t count = 0;
((index == count++ ? result = pushdata_deserialize_impl<DataClassCollection>(serialized) : std::monostate()), ...);
return result;
}
};
template <>
std::pair<int, int> pushdata_deserialize_impl<DataClass<int, int>>(const uint8_t *serialized) {
std::cout<<"Called deserialize for <int, int>"<<std::endl;
return std::make_pair<int, int>(1, 1);
}
template <>
std::pair<int, float> pushdata_deserialize_impl<DataClass<int, float>>(const uint8_t *serialized) {
std::cout<<"Called deserialize for <int, float>"<<std::endl;
return std::make_pair<int, float>(2, 2.0);
}
int main() {
// some input array to be processed
uint8_t serialized[3] = {0,1,2};
// works because no repetition of feeder types
DeSerializer<DataClass<int, int>, DataClass<int, float>> service_pushdata_working;
service_pushdata_working.pushdata(serialized, 2);
// does not work because there is a repetition of DataClass<int, float>
DeSerializer<DataClass<int, int>, DataClass<int, float>, DataClass<int, float>> service_pushdata_bugged;
service_pushdata_bugged.pushdata(serialized, 2);
return 0;
}
The important is the line
((index == count++ ? result = pushdata_deserialize_impl<DataClassCollection>(serialized) : std::monostate()), ...);
which iterates the types in the pack and if the index matches the count, the correct implementation of pushdata_deserialize_impl is called. This works fine as long as the types in the parameter pack are unique.
However, this pattern breaks down as soon as I have duplicate types in my parameter pack, like so:
DeSerializer<DataClass<int, int>, DataClass<int, float>, DataClass<int, float>>
How can I solve this? I cannot modify the DataClass type as it is given by another codebase.
Note that while the repeating type in the parameter pack may seem useless in this context, the actual implementation will trigger different logic for each type in the parameter pack, even if the types may be repeating.
One way is to generate compile-time indices to index into your variant:
First put std::monostate
at the end of the variant (or keep it at the front and do an Idx+1
later):
using VariantType = std::variant<std::pair<typename DataClassCollection::typeK, typename DataClassCollection::typeV>..., std::monostate>;
Then use the indices trick for generating a compile-time sequence:
auto pushdata_deserialize_helper(int index, const uint8_t *serialized) {
VariantType result;
size_t count = 0;
[&]<std::size_t... Idx>(std::index_sequence<Idx...>) {
((index == Idx ? (result.template emplace<Idx>(pushdata_deserialize_impl<DataClassCollection>(serialized)),0) : 0), ...);
}(std::index_sequence_for<DataClassCollection...>{});
return result;
}
Demo: https://godbolt.org/z/83aTo41Mf
Another way is to filter out the duplicate types of the std::variant
.
I'll use a typelist
type as a working type to avoid instantiating a bunch of unnecessary std::variant
specializations:
template<class...> struct typelist{};
First a type trait to filter out duplicate types from a typelist (adapted from How to filter duplicate types from tuple C++):
template <typename T, typename... Ts>
struct unique : std::type_identity<T> {};
template <typename... Ts, typename U, typename... Us>
struct unique<typelist<Ts...>, U, Us...>
: std::conditional_t<(std::is_same_v<U, Ts> || ...)
, unique<typelist<Ts...>, Us...>
, unique<typelist<Ts..., U>, Us...>> {};
template <typename... Ts>
using unique_typelist = typename unique<typelist<>, Ts...>::type;
Then a helper to apply a given template to a typelist:
template <template<class...>class Temp, class Typelist>
struct apply_typelist_impl{};
template <template<class...>class Temp, class... Args>
struct apply_typelist_impl<Temp, typelist<Args...>> {
using type = Temp<Args...>;
};
template <template<class...>class Temp, class Typelist>
using apply_typelist = typename apply_typelist_impl<Temp, Typelist>::type;
Then we use these to filter out duplicates from the std::variant
type:
using TypesForVariant =
unique_typelist<std::monostate,
std::pair<typename DataClassCollection::typeK, typename DataClassCollection::typeV>...>;
using VariantType = apply_typelist<std::variant, TypesForVariant>;
Demo: https://godbolt.org/z/E93Tjadf8
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With