Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++14 Metaprogramming: Automagically build a list of types at compile / init time

Using C++14 and some combination of the Curiously Recurring Template Pattern (CRTP) and possibly Boost.Hana (or boost::mpl if you wish), can I build a list of types at compile time (or static initialization time) without an explicit declaration?

As an example, I have something like this (see it on Coliru):

#include <iostream>
#include <boost/hana/tuple.hpp>
#include <boost/hana/for_each.hpp>

namespace
{
    struct D1 { static constexpr auto val = 10; };
    struct D2 { static constexpr auto val = 20; };
    struct D3 { static constexpr auto val = 30; };
}

int main()
{
    // How to avoid explicitly defining this?
    const auto list = boost::hana::tuple< D1, D2, D3 >{}; 

    // Do something with list
    boost::hana::for_each( list, []( auto t ) { std::cout << t.val << '\n'; } );
}

I want to avoid the explicit list of types -- D1, D2, and D3 -- in the creation of list because it means I have to maintain that list manually when it seems like I should be able to tell the compiler in or around the class declaration, "Add this class to your running list". (My ultimate aim is to automate factory registration, and this is the missing mechanism.)

Can I do this using some inheritance and/or metaprogramming trickery to compose the list at compile-time or static init time?

like image 415
metal Avatar asked Feb 06 '17 20:02

metal


2 Answers

To do it at compile-time will require "stateful" metaprogramming. In this article here, Filip Roséen explains how to implement the following using extremely advanced C++14:

LX::push<void, void, void, void> ();
LX::set<0, class Hello> ();
LX::set<2, class World> ();
LX::pop ();

LX::value<> x; // type_list<class Hello, void, class World>

Also, Matt Calabrese used similar techniques to implements semantic-based concepts in C++11, see the video and slides at slide #28.

Of course, these techniques rely on a compiler supporting conformant two-phase name lookup.

Alternatively, you can restucture your code to support runtime registration instead, which is much simpler, and can work portably across compilers such as MSVC. This is what libraries such as Prove or args use. It uses a generic auto_register class:

template<class T, class F>
int auto_register_factory()
{
    F::template apply<T>();
    return 0;
}

template<class T, class F>
struct auto_register
{
    static int static_register_;
    // This typedef ensures that the static member will be instantiated if
    // the class itself is instantiated
    typedef std::integral_constant<decltype(&static_register_), &static_register_> static_register_type_;
};

template<class T, class F>
int auto_register<T, F>::static_register_ = auto_register_factory<T, F>();

Then you can write your own CRTP class:

struct foo_register
{
    template<class T>
    static void apply()
    {
        // Do code when it encounters `T`
    }
};

template<class Derived>
struct fooable : auto_register<Derived, foo_register>
{};
like image 81
Paul Fultz II Avatar answered Oct 14 '22 14:10

Paul Fultz II


It sounds like you want to get a compile-time tuple of all types in a namespace or other scope. To do that, you would need static reflection, which has not yet been added to C++ (but would be very useful as you have discovered). You can read one proposal for static reflection here, and the N4428 proposal here.

As a workaround, you could write a macro to simultaneously define the type and implicitly add it to the registry during static initialization.

like image 4
George Hilliard Avatar answered Oct 14 '22 16:10

George Hilliard