C++14 Метапрограммирование: автоматическое построение списка типов во время компиляции / инициализации
Использование C++14 и некоторая комбинация шаблонов любопытных повторений (CRTP) и, возможно, Boost.Hana (или boost::mpl
если хотите), могу ли я создать список типов во время компиляции (или статической инициализации) без явного объявления?
Как пример, у меня есть что-то вроде этого (см. Это на 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'; } );
}
Я хочу избежать явного списка типов - D1
, D2
, а также D3
- в создании list
потому что это означает, что я должен поддерживать этот список вручную, когда мне кажется, что я должен быть в состоянии сказать компилятору в объявлении класса или вокруг него: "Добавить этот класс в свой рабочий список". (Моя конечная цель - автоматизировать регистрацию фабрики, а это недостающий механизм.)
Могу ли я сделать это, используя некоторую хитрость наследования и / или метапрограммирования для составления списка во время компиляции или во время статической инициализации?
3 ответа
Для этого во время компиляции потребуется метапрограммирование с сохранением состояния. В этой статье Филип Розен объясняет, как реализовать следующее с использованием чрезвычайно продвинутого 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>
Кроме того, Мэтт Калабрезе использовал аналогичные методы для реализации семантических концепций в C++11, см. Видео и слайды на слайде № 28.
Конечно, эти методы основаны на компиляторе, поддерживающем согласованный двухфазный поиск имен.
В качестве альтернативы вы можете перестроить свой код для поддержки регистрации во время выполнения, что намного проще и может работать переносимо между компиляторами, такими как MSVC. Это то, что используют такие библиотеки, как Prove или args. Он использует общий auto_register
учебный класс:
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>();
Затем вы можете написать свой собственный класс CRTP:
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>
{};
Похоже, вы хотите получить кортеж всех типов во время компиляции в пространстве имен или в другой области видимости. Для этого вам понадобится статическое отражение, которое еще не было добавлено в C++ (но, как вы обнаружили, было бы очень полезно). Вы можете прочитать одно предложение о статическом отражении здесь, и предложение N4428 здесь.
В качестве обходного пути вы можете написать макрос для одновременного определения типа и неявного добавления его в реестр во время статической инициализации.
Единственный способ сделать это прямо сейчас - это метапрограммирование с сохранением состояния, как описано здесь. Но это сложно, сложно реализовать, и комитет пытается исключить это как недействительный.