Я подумываю о создании функции dispatcher()
для сервера, указав пары {ID, LAMBDA} для сервера на выбор. Все идентификаторы уникальны. Предположим, у меня есть функции send()
и receive()
, поддерживающие все разрешенные типы. Как бы вы написали функцию dispatcher()
, которая получает идентификатор, выбирает соответствующую лямбда-функцию, получает ее параметры, вызывает ее и отправляет результат? У меня будет несколько экземпляров dispatcher()
для разных экземпляров сервера, каждый с разными наборами функций, и мне не нужны операторы переключения в реализации. Вот упрощенный скелет такого приложения (демо-версия компилятора):
#include <utility>
#include <vector>
template<class T>
auto receive() { return -1; } // for exposition only
void send(auto&& ... values);
template<std::pair...functions>
void dispatcher()
{
for(auto function_id = 0; function_id != -1;)
{
function_id = receive<int>();
// select function corresponding to function_id
// receive function arguments
// invoke function and send return value
}
}
int main()
{
dispatcher<{100, []{ return 100;}},
{1000, [](const std::vector<int>& v){ return v; }}>();
}
Похоже, кто-то пытается заново изобрести подмножество COM ... Примечание: его всегда изобретают плохо.
Являются ли эти ID
монотонно возрастающими int
? (1, 2, 3, 4, ...)
@catnip — я бы не хотел требовать заказа без необходимости.
Ваши лямбды не принимают одни и те же аргументы, поэтому часть «получения аргументов функции» становится интересной. Откуда они берутся? Как они упакованы? В каком-то кортеже? Если все ваши лямбды принимают аргументы одного и того же типа, проблем нет.
@gene В этом случае я бы согласился с предложением Реми, хотя Тед высказывает веское мнение.
Возьмите и верните какой-нибудь вариант формата (например, json).
@Тед Люнгмо - да, лямбды могут иметь разные аргументы. Конечно, их можно упаковать в кортежи и расширять при необходимости.
@Gene Возможно, вам следует упаковать как принимающую, так и отправляющую лямбды вместе, чтобы не выполнять два ID
поиска? Я сделал эту демо-версию только для приемника. Это похоже на то, что вам нужно?
@Тед Люнгмо - Я думаю, что выражение сгиба, как в вашей демонстрации, - правильный способ решить эту проблему. Для лямбд-выражений необходим признак для извлечения аргументов, после чего их можно обернуть в кортеж.
@Gene Если вы свяжете три части (получение аргументов функции, вызов функции с ними и отправку результата) вместе, это станет менее проблематичным. Тогда каждая лямбда будет автономной, состоящей из всех трех частей, и не будет необходимости в специальном обращении.
@Тед Люнгмо - у тебя есть пример? Если я вас правильно понимаю, вам нужен кортеж из всех трех, но тогда кортеж не является структурным типом.
@Gene Нет, я имею в виду, что вы создаете пару идентификатора и лямбды. Внутри лямбды он извлекает аргументы, которые могут быть особенными для каждой лямбды, вызывает функцию с этими аргументами и отправляет результат, который также может быть разным для каждой лямбды.
Это решение основано на следующих двух шагах:
Сначала мы создаем выражение-свертку (предложенное @Ted Lyngmo), которое
приводит к вызову функции executor()
, соответствующей лямбда-выражению
идентификатор функции.
Во-вторых, мы используем свойства лямбда-функции для определения типов параметров. необходимо получить эти параметры, передав их в соответствующую лямбда-функцию и отправку результата выполнения.
Следующий код демонстрирует этот подход (Демонстрация Compiler Explorer):
template<class Fn> struct lambda_traits;
template< class R, class G, class ... A >
struct lambda_traits<R(G::*)(A...) const>
{
using Args = std::tuple<std::remove_cvref_t<A>...>;
};
template<class Fn>
struct lambda_traits : lambda_traits<decltype(&Fn::operator())>{};
template<class Fn>
using lambda_args = typename lambda_traits<std::remove_cvref_t<Fn>>::Args;
template <std::pair function>
auto executor() {
// receive function arguments for function.first here
std::cout << "Executing function ID " << function.first << '\n';
std::apply([&](auto&&...args)
{
(receive(args), ...);
send(std::invoke(function.second, std::forward<decltype(args)>(args)...));
}, lambda_args<decltype(function.second)>{});
}
template<std::pair...functions>
void dispatcher()
{
for(auto function_id : {10, 100})
{
//function_id = receive<int>();
(void)((functions.first == function_id
&&
(executor<functions>(), true)) || ...);
}
}
int main()
{
dispatcher<{100, []{ return 123;}},
{10, [](const std::vector<int>& v){ return v; }}>();
}
Вам нужно обернуть receive<Args...>()
в std::initializer_list
(чтобы инициализировать кортеж), чтобы иметь гарантированный порядок вычислений.
@Jarod42 - хороший улов, оператор запятая тоже это сделает.
@Jarod42 - нарушает ли обработка ссылок rvalue как ссылок lvalue, пока существует временное значение, что-либо в Стандарте?
Насколько я понимаю, ваш вопрос нет. Кстати, у них есть имя, есть l-значения.
Обычно этот вид диспетчеризации во время выполнения обрабатывается с использованием
std::(unordered_)map
идентификаторов, сопоставленных с указателями на функции (или лямбда-выражениями, хранящимися какstd::function
). Но функции/лямбды должны иметь общую подпись, если только вы не обернете ихstd::variant
или эквивалентом. Я бы не стал пытаться сделать это, используя аргументы шаблона в диспетчере.