Может ли C++11/14 шаблон с переменным числом аргументов перебирать аргументы функции?
Я использую шаблоны переменных для сбора статической информации о типах в Isis2, атомарной многоадресной библиотеке (isis2.codeplex.com). Некоторые события Isis2 доставляются через upcall. Например, если вы код
Group g("myGroup");
g.Handlers[UDPATE] += [](string& name, Foo& f) { ... your code };
....
g.OrderedSend(UPDATE, "John Doe", new Foo(...));
затем при получении многоадресной рассылки в группе g, содержащей обновление со строкой и объектом Foo, Isis2 создаст локальный экземпляр объекта Foo и затем вызовет эту лямбду с соответствующими аргументами.
Так вот моя загадка. У меня есть код переменной для сканирования аргументов в OrderedSend и я могу захватить информацию статического типа, необходимую для создания моих сообщений. В итоге я передаю реальному методу OrderedSend одномерный массив аргументов, каждый из которых имеет свой тип, указатель или безопасную ссылку на данные или объект, а для объекта - адрес метода маршалинга. Но чтобы использовать шаблон переменной для сканирования лямбды, мне нужно взглянуть на "внутренний список аргументов" функции в том смысле, что объект, добавляемый в вектор обработчиков, является лямбда-выражением: методы type_traits просто скажут, что является объектом типа "функция". Я после строки и типов Foo, из списка аргументов лямбда. Но, как я вижу, в type_traits.h ничего нет для доступа к списку аргументов.
Специфичная опция GCC-11 - отменить ввод идентификатора и проанализировать полученную строку. Но есть ли особенность шаблона variadic, которая позволила бы мне получить список аргументов лямбды во время компиляции?
2 ответа
template<class Sig>
struct MessageName {
std::string name;
MessageName() = delete;
MessageName( std::string o ):name(o) {}
MessageName(MessageName&&)=default;
MessageName(MessageName const&)=default;
MessageName& operator=(MessageName&&)=default;
MessageName& operator=(MessageName const&)=default;
};
// trait to determine if some args are compatible:
template<class Sig, class...Ts>
struct is_compatible : std::false_type {};
template<>
struct is_compatible<void()> : std::true_type {};
template<class A0, class...Args, class T0, class...Ts>
struct is_compatible<void(A0, Args...), T0, Ts...>:
std::integral_constant<bool,
std::is_convertible<T0, A0>::value
&& is_compatible< void(Args...), Ts... >::value
>
{};
struct HandlerMap {
template<class Sig>
void add_handler(
MessageName<Sig> msg,
block_deduction< std::function<Sig> > handler
)
{
// ...
}
template<class Sig, class...Ts>
typename std::enable_if<is_compatible<Sig, Ts...>::value>::type
send_message( MessageName<Sig> msg, Ts&&... ts )
{
// ...
}
};
UPDATE
токен должен быть типа MessageName
, Все MessageName
s должен требовать подпись, связанную с ними.
MessageName< void(std::string const&, Foo const&) > UPDATE{"update"};
как и выше.
Затем, когда вы добавляете обработчик, вызов add_handler
проверит назначенную функцию на соответствие требуемой подписи и даст вам std::function
,
Точно так же, когда вы отправляете сообщение, переданные типы могут быть проверены на соответствие подписи. Вы должны даже преобразовать аргументы в каждый из типов аргументов подписи в теле функции.
Это перемещает как можно большую часть проверки типов во время компиляции, что является хорошим стилем C++.
Нет, это невозможно. Что если объект не лямбда, а структура с перегрузками? Или полиламбда? Вы не можете предполагать, что объект функции имеет одну и только одну подпись - существует множество способов получить более одной.
Вот простой пример:
struct fun {
int i;
void operator()(int x) {}
void operator()(float x) {}
};
В этой структуре или ее аргументах нет ничего сверхсложного или не-POD.