C++ Metaprogrammation real use case

Posted in C++ with tags C++ -

Context

In a recent project, we had a C++ backend and Scala frontend (Scala is a functional JVM based language). The C++ backend was responsible for creating some objects and the frontend job was to sort the objects into different sets according to some rules defined in custom script parsed in Scala. The objects in the frontend were represented by a pair containing their address casted into uintptr_t (with an reinterpret_cast) and an enumeration value about their real nature.

To sort the objects, the frontend needed to know some properties about the objects. To retrieve them, it would pass the pair back to the backend, then the backend would cast the integer to the right pointer nature, perform the operation and send back the result.

Problem

Let assume a want to retrieve a property fon some objects whose types are B or C.

enum class nature_t{A_t,B_t,C_t};

struct A{
  virtual ~A() = default;
  virtual int f() = 0;
};
struct B : A {
  int f() override { ... }
};

struct C : A {
  int f() override { ... }
};

The initial code to do it would looked like this

int f_prop(uintptr_t ptr, nature_t nature) {
  return reinterpret_cast<A *>(ptr)->f();
}

But according to this page, it is only safe to deference a pointer obtained from a reinterpret_cast if the cast uses the same pointer type. Here, we were doing an upcasting at the same time. To be safe, he code has to look like this

int f_prop(uintptr_t ptr,nature_t nature){
	if (nature == nature_t::B_t) {
		auto ptr2 = reinterpret_cast<B *>(ptr);
		return ptr2->f();
	}
	if (nature == nature_t::C_t) {
		auto ptr2 = reinterpret_cast<C *>(ptr);
		return ptr2->f();
	}
}

which is tedious and error prone.

Designing the solution

The ideal solution would be something like this (this not real code)

int f_prop(uintptr_t ptr,nature_t nature){
	for(current_type : {C,B}) {
		if (nature == lookup(current_type)){
			auto ptr2 = reinterpret_cast<current_type*>(ptr);
			return ptr2->f();				
		}
	}
}

That would be neat, wouldn’t it ? Guess what, we can achieve something very close to this with template meta programming using boost::mpl0.

First of all, we need to build a relation between types and enumeration values within nature_t.

We use a boost::mpl::map between a nature_t values (converted into types) and pointer to class within the hierarchy. We can build types from int values using mpl::int_. Since nature_t is strongly enum nature, we have to cast it to its underlying type. That cast is done by the to_integral functions. The cast from integer to nature_t by to_enum. We just need to wrap this map inside a template function lookup that will return the right enum value for a given type.

template<class T>
nature_t to_enum(T e) { return static_cast<cpp_nature_t>(e); }

template <typename E>
constexpr auto to_integral(E e) -> typename std::underlying_type<E>::type {
  return static_cast<typename std::underlying_type<E>::type>(e);
}

template <class type> nature_t lookup() {
  using boost::mpl::pair;
  using boost::mpl::int_;

  using lookup_table = boost::mpl::map<
      pair<B *, int_<to_integral(nature_t::B_t)>>,
      pair<C *, int_<to_integral(nature_t::C_t)>>
	  >;
  using lookup_result = typename boost::mpl::at<lookup_table, type>::type;
  return to_enum(lookup_result::value);
}

We can use a boost::mpl::vector to declare a list of types and iterate through it with boost::mpl::for_each. mpl::for_each is similar to std::for_each, it applies a given function to each type within its argument. That function has a single (obviously template) argument. But we also need ptr (remember, we want to reinterpret_cast ptr automatically to the right type). So will use C++14 polymorphic lamba.

The lamba will compare the lookup result of the current type to nature value and if they match, perform the cast.

int f_prop(uintptr_t ptr,nature_t nature){
		using accepted_types = boost::mpl::vector<B*,C*>;
		boost::mpl::for_each<accepted_types>(
			[ptr,nature](auto type_carrier){
			      if (nature == lookup<type>()) {
					  return reinterpret_cast<type>(ptr)->f();
				  }
			}
		)
	}

Obviously, I have more than one property, so I will factorize the looping / casting into a try_cast_apply function.

template <class accepted_types, class F>
	auto try_cast_apply(F prop, uintptr_t ptr, nature_t nature) {
  boost::mpl::for_each<accepted_types>([nature, ptr, f](auto type_carrier) {
    using type = decltype(type_carrier);
    if (nature == lookup<type>()) {
      return prop(reinterpret_cast<type>(ptr));
    }
  });
}

and f_prop looks like this

	int f_prop(uintptr_t ptr,nature_t nature){
		using accepted_types = boost::mpl::vector<B*,C*>;
		return try_cast_apply<accepted_types>([](auto obj){
			return obj->f();
		}, ptr,nature);
	}

The major drawback ? Compile time grows longer.


boost::mpl is not the only library for doing template meta programmation. We could also have used brigand The main differences are that mpl::vector becomes a brigand::list and the for_each function expects an argument of type brigand::type_<T>.

Written by Davidbrcz
Later article
Some news