Skip to content

Instantly share code, notes, and snippets.

@Mr-Andersen
Last active September 25, 2023 14:42
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Mr-Andersen/cd5f9d8740e03b3a779bd49da281652d to your computer and use it in GitHub Desktop.
Save Mr-Andersen/cd5f9d8740e03b3a779bd49da281652d to your computer and use it in GitHub Desktop.
Implementation of tagged union without typeinfo library. Beware: this code contains a dozen undefined behaviours and a dozen more. Use only as inspiration.
#include <iostream>
// get_type<i, Ts...>::type -> Ts[i] (Compile error of i >= length(Ts))
template<size_t i, typename T, typename... Ts>
struct get_type {
typedef typename get_type<i - 1, Ts...>::type type;
};
template<typename T, typename... Ts>
struct get_type<0, T, Ts...> {
typedef T type;
};
// Ts[index_type<A, Ts...>::value] == A (Compilation error if A not in Ts)
template<typename A, typename T, typename... Ts>
struct index_type {
static const size_t value = index_type<A, Ts...>::value + 1;
};
template<typename A, typename... Ts>
struct index_type<A, A, Ts...> {
static const size_t value = 0;
};
template<typename T>
constexpr T max(const T& a, const T& b) {
return a > b ? a : b;
}
// max_size<Ts...>::value
template<typename T, typename... Ts>
struct max_size {
static const size_t value = max(sizeof(T), max_size<Ts...>::value);
};
template<typename T>
struct max_size<T> {
static const size_t value = sizeof(T);
};
template<typename... Ts>
class Variant {
size_t curr_type;
char data[max_size<Ts...>::value];
public:
Variant():
curr_type(),
data() {}
template<typename T>
Variant(const T& x):
curr_type(index_type<T, Ts...>::value)
{
auto ptr = reinterpret_cast<const char*>(&x);
std::copy(ptr, ptr + sizeof(T), this->data);
}
template<typename T>
const T& operator=(const T& x) {
this->curr_type = index_type<T, Ts...>::value;
auto ptr = reinterpret_cast<const char*>(&x);
std::copy(ptr, ptr + sizeof(T), this->data);
return x;
}
template<typename T>
bool is() const {
return this->curr_type == index_type<T, Ts...>::value;
}
template<typename T>
T& as() {
if (!this->is<T>())
throw std::runtime_error("Requested type is not contained");
return *reinterpret_cast<T*>(this->data);
}
template<typename T>
bool into(T& x) {
if (!this->is<T>())
return false;
x = *reinterpret_cast<T*>(this->data);
return true;
}
};
int main() {
Variant<short unsigned int, long int [3]> v;
v = (long int [3]) {10, -123, 345};
if (v.is<long int [3]>()) {
std::cout << v.as<long int [3]>()[0] << std::endl;
}
}
@csrichter
Copy link

csrichter commented Feb 3, 2022

Thanks for this very usefull code snipped.
I was able to implement a variant type for our (c++14 based) project

a few suggestions:

  • make is() const
  • add a const overload to as (which casts to const T*)

I also have a question: does this code have problems with data alignment?

AFAIK it would be ok on a 32bit platform and if no types larger than 32bit are used (since size_t curr_type has alignment 4 the variant also has alignment 4)
But if a smaller type is used for curr_type, or types with larger/more strict alignments are stored in the variant, it might lead to problems

i'm no expert in c++, but declaring the class like this might work:

template<typename... Ts>
class alignas(Ts...) Variant

@Mr-Andersen
Copy link
Author

Oh yes, it definitely has problems. You are probably right about alignment, also this thing does not call destructor on inner object (that's easy to fix though). You are right about const stuff. This could also use a move-constructor.
I suspect there are many more problems with this code, so I'd rather erase this gist at all, actually :) I've written it when I knew C++ less, and the more you know it, more insecure you get about your C++ code. I don't want novices get confused by it.
Thank you for writing back and I am happy it helped you!

@csrichter
Copy link

i don't think, that you should delete the gist.
It was helpful to me, because it is a simple (=short) and therefore understandable implementation of a variant type.

I think these comments (or a comment in the original source) are enough to document the possible problems with this code and the things a user of this might want to think about

@nstansbury
Copy link

I agree, this Gist was very helpful in me solving an identical problem I had in passing sensor data around on an RP2040/Arduino platform.

I never full understood the templating language you used, but it inspired me to work on a slightly different templated approach for a variant KeyValue pair propery set:

  PropertySet props = PropertySet();

  // Set the property key/value pair
  props.setProperty("IntProp", 12345);
  props.setProperty("StrProp", std::string("A string property"));
  props.setProperty("ArrProp", std::array<int, 5>({1,2,3,4,5}));


  // Get the property values based on the key
  auto intProp = props.getProperty<int>("IntProp");
  auto strProp = props.getProperty<std::string>("StrProp");
  auto arrProp = props.getProperty<std::array<int, 5>>("ArrProp");

  // Get the underlying Property descriptor
  PropertyType::shared_ptr propType = props.getPropertyType("IntProp");
  if(propType->typeOf<int>()){
    std::shared_ptr<PropertyDescriptor<int>> propertyDesc;
    propertyDesc = propType->getDescriptor<int>();
    Serial.print("\nIntProp Descriptor Value: ");
    Serial.print(propertyDesc->value());
  }

Full Gist here: https://gist.github.com/nstansbury/6d627a447ca3b4ebbcf2c49ae37922c5

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment