Avinash
Avinash

Reputation: 13257

C++ reflection how it is achieved

I know that C++ does not support reflection, but I went through paper Reflection support by means of template meta-programming , But did not understand how this is achieved. Would anybody have more details or examples on how this can be achived in C++ using template meta-programming?

Upvotes: 5

Views: 5716

Answers (2)

Dude
Dude

Reputation: 583

It is possible to query certain characteristics of a type at compile time. The simplest case is probably the built-in sizeof operator. As MadScientist posted, you can also probe for specific members.

Within frameworks that use generic programming or template metaprogramming there are typically contracts on the synopsis of classes (formalized as Concepts).

The STL for instance uses a member typedef result_type for function objects. boost:result_of (that later became std::result_of) extended this contract to allow a nested class template in order to compute the result type of a function object whose parameters were generic (in other words - having an overloaded or template operator()). Then boost:result_of would perform compile time reflection to allow client code to determine the result type of a function pointer, STL function object or "templated function object" uniformly allowing to write more generic code that will "just work" in either case. Sidenote: C++11 can do better in this particular case - I used it as the example because it is both nontrivial enough and based on widespread components.

Further it is possible to use client code that will emit a data structure that contains meta information deduced at compile time (or even passed-in by client code) when registering a certain type. The framework code could e.g. use the typeid operator to obtain a runtime representation of the type and generate call stubs for a specific constructor, the destructor and a set of member functions (some might be optional) and store this information in a std::map keyed by std::type_index (or a hand-written wrapper around std::type_info for older versions of the language). At a later point in the program this information can be found given (a runtime representation of) some object's type in order to run an algorithm that e.g. creates more instances of the same type where some have temporary lifetime, run some operations and tidy up.

Combining both techniques is very powerful, because code that is to be run at high complexity can be generated at compile time using aggressive inlining for possibly many variations generated from templates on the fly, interfacing with less time-critical parts by similar means at program run time.

Upvotes: 0

MadScientist
MadScientist

Reputation: 3460

Here is an example of a struct that tests at compile time if a Type of type Obj has a public data member of type Type that is named "foo". It uses C++11-features. While it can be done using C++03-features, I consider this approach superior.

First, we check if Obj is a class using std::is_class. If it is not a class, it cannot have data members so the test returns false. This is achieved with the partial template specialization below.

We will use SFINAE to detect if the object contains the data member. We declare the struct helper that has the template parameter of type "pointer to data member of type Type of the class Obj". Then we declare two overloaded versions of the static function test: The first, which rsturns a type indicating a failed test accepts any parameter via the ellipsis. Note that the ellipsis has the lowest precendence in overload resolution. The second, which returns a type indicating success, accepts a pointer to the helper struct with template parameter &U::foo. Now we check what a call to test with U bound to Obj returns if called with a nullptr and typedef that to testresult. The compiler tries the second version of test first since the ellipsis is tried last. If helper<&Obj::foo> is a legal type which is only true if Obj has a public data member of type Type then this overload is chosen and testresult will be std::true_type. If this is not a legal type the overload is excluded from the list of possible candidates (SFINAE) so the remaining version of test which accepts any parameter type will be chosen and testresult will be std::false_type. Finally, the static member value of testresult is assigned to our static member value which indicates whether our test was successful or not.

One downside of that technique is that you need to know the name of the data member you are testing explicitly ("foo" in my example) so to do that for different names you would have to write a macro.

You can write similar tests to test if a type has a static data member with a certain name and type, if it has an inner type or typedef with a certain name, if it has a member function with a certain name that can be called with given parameter types and so on but that exceeds the scope of my time right now.

template <typename Obj, typename Type, bool b = std::is_class<Obj>::value>
struct has_public_member_foo
{
  template <typename Type Obj::*> 
  struct helper;

  template <typename U>
  static std::false_type test(...);

  template <typename U>
  static std::true_type test(helper<&U::foo> *);

  typedef decltype(test<Obj>(nullptr)) testresult;

  static const bool value = testresult::value;
};

template <typename Obj, typename Type>
struct has_public_member_foo<Obj, Type, false> : std::false_type { };

struct Foo
{
  double foo;
};

struct Bar
{
  int bar;
};


void stackoverflow() 
{
  static_assert(has_public_member_foo<Foo, double>::value == true, "oops");
  static_assert(has_public_member_foo<Foo, int>::value == false, "oops");
  static_assert(has_public_member_foo<Bar, int>::value == false, "oops");
  static_assert(has_public_member_foo<double, int>::value == false, "oops");
}

Upvotes: 7

Related Questions