In some languages (eg, on dotnet), tagged unions are implemented through an OOP hierarchy, where you have, for example, something like:
public sealed abstract class Option<T> {
private Option() { }
public sealed class Some<T> : Option<T> {
private readonly T value;
public Some(T value) {
this.value = value;
}
}
public sealed class None<T> : Option<T> {
public None() { }
}
public static Option<T> Some(T value) { return new Some(T); }
static readonly Option<T> _None = new None();
public static Option<T> None() { return _None; }
}
Where you can use `Option.Some` or `Option.None`, and the return type is inferred from the parameter.
The sizeof(None) is not the same as sizeof(Some<T>) - the Some class has an additional field not present in the None class - and the size of Some depends on the type parameter T.
If the objects have RTTI, we don't even need a tag field, as there is an implicit tag in the form of type information in the object - the pattern matching facility can test `opt is Some<T>` or `opt is None`.
In C we would have to use a pointer to option to do the equivalent, eg with some GCC extensions we can do:
enum option_tag { OPT_NONE, OPT_SOME };
typedef struct option { enum_option_tag tag; } *Option;
struct none { struct option base; } _none = { .base.tag = OPT_NONE };
Option None = (Option)&_none;
#define Some(T, _value) \
({ \
struct some_##T { \
struct option base; \
T value; \
} *result = GC_alloc(sizeof(struct some_##T)); \
result->base.tag = OPT_SOME; \
result->value = (_value); \
(struct option*)result; \
})
bool is_some(Option opt) {
return opt->tag == OPT_SOME;
}
bool is_none(Option opt) {
return opt->tag == OPT_NONE;
}
#define _get_some(T, opt) (((struct some_##T*)(opt))->value)
This ONLY works if the tag is the first field - casting between pointers to different types is UB if the first fields are not the same.
Not the most efficient way to implement an Option in C, but for other tagged unions, if any of the values are particularly large this may be beneficial.
A frequent implementation method for unions does not need to change the pointer, because the tag is accessed with a negative offset and the pointer always points to the value.