3
\$\begingroup\$

My aim with the following is to have a way of registering a list of classes, which represent plugins. In the end, I want a boost::hana::set that contains type objects for all plugins' classes. I want everything to be as loosely coupled as possible: Basically, I should be able to just delete a plugin's files, and everything should still compile and run smoothly. I also should be able to copy in a new plugin, and compilation should pick it up. Everything should be evaluated at compile time.

I already have some find-and-include-all-include-files magic based on CMake up and running. Just assume that every plugin has an include file named "plugin_something.hpp", which is included in the code below.

The Basic Idea

The basic idea behind the code below is: Have a struct template that takes a number as template parameter. Every plugin first checks for which numbers this template has already been specialized. It then picks the next number and specializes the template. The template being specialized has a method that recurses on the specialization with the next smaller number - from there it gets the plugins that have been registered so far. It then appends its own class to the set and returns it.

The Code

Plugin Registration

/* ==============================================================================
 *    plugins.hpp - providing methods for plugin registration
 * ============================================================================== */

template<class>
struct sfinae_true : std::true_type{};

/* This template will be specialized by every plugin with its own integer.
 * The operator() of the respective struct should return a hana::set of all the 
 * classes registered so far. */
template<unsigned int>
struct registry_hook;

namespace registry {
  /* Methods to test whether a registry_hook with a specified number is already
   * defined via SFINAE. */
  template<class Dummy, unsigned int testN>
  constexpr auto test(int)
      -> sfinae_true<decltype(registry_hook<testN>{})>;
  template<class Dummy, unsigned int testN>
  constexpr auto test(long) -> std::false_type;

  /* Returns the smallest number N for which registry_hook<N> is not defined yet. 
   * A per-plugin unique dummy class is necessary s.t. this struct is re-instantiated for 
   * every plugin.
   */
  template<class Dummy, unsigned int testN = 0>
  constexpr unsigned int get_free_N() {
    /* The 'constexpr' is essential here. Otherwise, the template in the
     * else-branch must be instantiated even if the condition evaluates to true, 
     * leading to infinite recursion. */
    if constexpr (std::is_same<decltype(test<Dummy, testN>(0)), std::false_type>::value) {
        return testN;
    } else {
        return get_free_N<Dummy, testN + 1>();
    }
  }

  /* Helper struct / method to chain together the registered classes */
  template<class ClassToRegister, unsigned int myN> 
  struct register_class {
      /* General case: myN > 0. I.e., other classes have already been registered.
       * We recursively get the set of these classes and append our own. */
      auto operator()() {
          return hana::insert(registry_hook<myN - 1>{}(), hana::type_c<ClassToRegister>);
      }
  };
  template<class ClassToRegister> 
  struct register_class<ClassToRegister, 0> {
      /* Special case: myN == 0. No other classes have been registered. Create a new
       * set. */
      auto operator()() {
          return hana::make<hana::set_tag>(hana::type_c<ClassToRegister>);
      }
  };

} 

Plugins

/* ==============================================================================
 *    Plugin1.hpp - registering "char"
 * ============================================================================== */
struct Dummy1;
template<>
struct registry_hook<registry::get_free_N<Dummy1>()> {
    constexpr static unsigned int my_N = registry::get_free_N<Dummy1>();

    auto operator()() {
        return registry::register_class<char, my_N>{}();
    }
};

/* ==============================================================================
 *    Plugin2.hpp - registering "int"
 * ============================================================================== */
struct Dummy2;
template<>
struct registry_hook<registry::get_free_N<Dummy2>()> {
    constexpr static unsigned int my_N = registry::get_free_N<Dummy2>();

    auto operator()() {
        return registry::register_class<int, my_N>{}();
    }
};

/* ==============================================================================
 *    Plugin3.hpp - registering "std::string"
 * ============================================================================== */
struct Dummy3;
template<>
struct registry_hook<registry::get_free_N<Dummy3>()> {
    constexpr static unsigned int my_N = registry::get_free_N<Dummy3>();

    auto operator()() {
        return registry::register_class<std::string, my_N>{}();
    }
};

Main

/* ==============================================================================
 *    main.cpp - binding it all together
 * ============================================================================== */


int main() {
    // retrieve the maximum N
    constexpr unsigned int max_N = registry::get_free_N<void>() - 1;

    // get the set of registered classes
    auto registered_classes = registry_hook<max_N>{}();

    BOOST_HANA_CONSTANT_CHECK(
        hana::make_set(hana::type_c<char>, hana::type_c<int>, hana::type_c<std::string>) == 
        registered_classes);
}

Questions

  • Does anything of the above have a bad smell? Does it blow up if I use a strange compiler?
  • Can I somehow (in a not-undefined way…) get rid of the dummy classes?
  • Do you see something that could be simplified?

Credits

Credit where credit is due: I adapted code from https://stackoverflow.com/a/9154394/4694124 for the "is this struct implemented"-check.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.