Skip to content

Instantly share code, notes, and snippets.

@pniedzielski
Last active November 3, 2020 07:10
Show Gist options
  • Save pniedzielski/0e6c48c0ac59ab16ed29 to your computer and use it in GitHub Desktop.
Save pniedzielski/0e6c48c0ac59ab16ed29 to your computer and use it in GitHub Desktop.
Type-safe generation of variant<Ts...> in C++
// Say we want something that generates a T when it is called with
// operator():
template <typename G, typename T>
concept bool Generator = requires (G g) {
{ g() } -> T
};
// For instance, this is a Generator of int
int gimmeRandomInt() { return rand(); }
// As is this:
int gimmeZero() { return 0; }
// Or more interestingly, objects of this type template is a generator
// of an arbitrary T:
template <typename T>
struct generate {
T operator() ();
};
// For example,
template <>
struct generate<int> {
auto operator() () { return rand(); }
};
// which can be called as generate<int>{}() (construct new
// generate<int> with braces, then call it with parens).
// Interesting case is for variant on arbitrary Ts... though.
template <typename... Ts>
struct generate<variant<Ts...>> {
variant<Ts...> operator() ();
};
// We know how to easily generate a variant given a type to put in it:
/*
template <typename T, typename... Ts>
struct generate_variant_from_T {
variant<Ts...> operator() () {
auto generator = generate<T>{};
return variant<Ts...>{ generator() };
}
};
*/
// However, this will give us a different type for every T. Because
// we need to select from a set of generators at runtime (based on a
// randomly generated value), we need them to be of the same runtime
// type. Thus, we need to erase the compile time type, using runtime
// polymorphism.
namespace details {
// Abstract Base Class for all variant generators.
template <typename... Ts>
struct generate_variant {
virtual variant<Ts...> operator() () = 0;
};
// Make a template for which all instantiations derive from
// generate_variant<Ts...>
template <typename T, typename... Ts>
struct generate_variant_from_T : public generate_variant<Ts...> {
virtual variant<Ts...> operator() () override {
auto generator = generate<T>{};
return variant<Ts...>{ generator() };
}
};
}
// If we make pointers to each of these on the heap, the runtime type
// is generate_variant<Ts...>, but the compile-time type is
// generate_variant_from_T<T, Ts...>. This means we can do dynamic
// dispatch and generate a variant with the given type T in a typesafe
// way at runtime, for an arbitrary T or Ts... Our actual generator
// looks like this:
template <typename Ts...>
struct generate<variant<Ts...>> {
auto operator() () {
using namespace details;
// First, we make all generators. We create a generate_variant
// pointer for each type T in Ts... We could use an array
// instead, because the size is known at compile time (one for
// each Ts...).
auto generators = vector<unique_ptr<generate_variant<Ts...>>>{
make_unique( generate_variant_from_T<Ts, Ts...>{} )...
};
// Now, we select a random index into the vector.
auto random_index = get_rand_between(0, size(generators));
// Finally, we select that generator, and use it to generate a
// variant.
auto& generator = generators[random_index];
return generator();
}
};
// Now, for any variant<Ts...>, we can generate a value of it over its
// entire range. If get_rand_between has some more information about
// each type (we make a metafunction that says how many possible
// values there are for each T in Ts...), we can make this generate
// uniformly across the entire space of values of variant<Ts...>.
// If we know the layout of our types (implementation-defined, but the
// classes are empty and the vtable layouts will probably look the
// same), we can even avoid the need for the heap memory all-together.
// Doing so is unsafe, but, along with devirtualization, would make
// this trivially inlinable and very fast.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment