C++ обратный вызов с переменным числом аргументов

Функции с переменным числом аргументов в C++ позволяют пользователю вызывать функцию с произвольным количеством аргументов. Например, sscanf принимает в качестве входных данных строку для анализа, строку формата и набор параметров, которые будут принимать значения анализируемых элементов. Что, если бы я захотел сделать эту парадигму асинхронной? Мне нужно разобрать некоторые данные, извлекая переменное количество параметров из некоторых байтов. Параметры, которые необходимо извлечь, указываются в строке формата, как в sscanf. Я хотел бы, чтобы моя функция вызывалась так:

function <void (int, int, int)> myfunc = [=] (int a, int b, int c)
{
    // Do something here!
};

asyncsscanf(my_bytes, "iii", myfunc);

asyncsscanf должен выполнить необходимую обработку, и по завершении я хотел бы, чтобы он вызывал myfunc с правильными аргументами, указанными в строке формата. Возможно ли сделать подобное?

Спасибо


person Matteo Monti    schedule 03.01.2015    source источник
comment
Зачем вам "iii", когда у вас есть исходные типы?   -  person Yakk - Adam Nevraumont    schedule 03.01.2015
comment
Я не уверен, что понимаю вашу точку зрения. Не могли бы вы объяснить это более подробно?   -  person Matteo Monti    schedule 04.01.2015
comment
Вы передаете "iii" своей функции. Почему? myfunc имеет тип function<void(int,int,int)> -- зачем передавать строку с дубликатом этой информации? Зачем использовать данные времени выполнения?   -  person Yakk - Adam Nevraumont    schedule 04.01.2015
comment
Спасибо! Японял твою точку зрения. Я даже не знаю, как это сделать с этой дублирующейся информацией! Не могли бы вы подсказать, как бы вы это сделали?   -  person Matteo Monti    schedule 04.01.2015
comment
Можешь написать int read_int(byte*&)? Или что-то эквивалентное? (синхронное чтение из байтов в одно значение, в этом случае обновляет ввод после прочитанных данных). В этом много шаблонов и много решений, и я думаю, что в основном они не связаны с вашей проблемой.   -  person Yakk - Adam Nevraumont    schedule 04.01.2015


Ответы (1)


Я не знаю, как это сделать с вашим подходом.

Во-первых, нет подходящего типа для третьего параметра функции asyncscanf. В теле asyncscanf допустимо только значение void(...) (функция принимает неограниченное количество аргументов и ничего не возвращает), но тогда аргумент, который вы передаете asyncscanf в качестве третьего, должен быть того типа, который, вероятно, неприемлем.

Во-вторых, вы должны выполнить отправку my_bytes в соответствии с форматом («iii» в примере). Это можно сделать, если у вас есть конечное количество строк различных форматов (тогда вы можете «переключаться» между всеми возможными форматами). Но в общем случае я полагаю, что это невозможно сделать.

Но поскольку вы отмечаете свой вопрос «вариативными шаблонами», я предполагаю, что вы используете С++ 11/14. Возможно, вы хотели бы сделать аргумент формата asyncscanf аргументом шаблона, который был бы более удобочитаемым (я предполагаю, что формат всегда известен во время компиляции). Ниже фрагмент решения.

#include <functional>
#include <iostream>

// template parsing function, remaining_bytes parameter should contain pointer to not parsed part
// of my_bytes
template <typename return_type> return_type parse(const char *my_bytes, const char *&remaining_bytes);

// specialization of parsing function for desired types, fake implementation
template <> int parse<int>(const char *my_bytes, const char *&remaining_bytes) {
    remaining_bytes = my_bytes;
    return 0;
}

// specialization of parsing function for desired types, fake implementation
template <> long parse<long>(const char *my_bytes, const char *&remaining_bytes) {
    remaining_bytes = my_bytes;
    return 1;
}

// declare helper template for general case
template <typename to_be_parsed_tuple, typename parsed_tuple>
struct asyncscanf_helper;

// all params parsed case
template <typename... parsed_params>
struct asyncscanf_helper<std::tuple<>, std::tuple<parsed_params...>> {
    void operator()(const char *, std::function<void(parsed_params...)> fun, parsed_params... params) {
        fun(params...);
    }
};

// some params to be parsed case
template <typename first_param_to_be_parsed, typename...to_be_parsed_params, typename... parsed_params>
struct asyncscanf_helper<std::tuple<first_param_to_be_parsed, to_be_parsed_params...>, std::tuple<parsed_params...>> {
    void operator()(const char *my_bytes, std::function<void(parsed_params..., first_param_to_be_parsed, to_be_parsed_params...)> fun, parsed_params... params) {
        const char *remaining_bytes = 0;
        first_param_to_be_parsed p1 = parse<first_param_to_be_parsed>(my_bytes, remaining_bytes);
        asyncscanf_helper<std::tuple<to_be_parsed_params...>, std::tuple<parsed_params..., first_param_to_be_parsed>>()(remaining_bytes, fun, params..., p1);
    }
};

template <typename... params>
void asyncscanf(const char *my_bytes, void function(params...)) {
    asyncscanf_helper<std::tuple<params...>, std::tuple<>>()(my_bytes, function);
}

void test_fun(int a, int b, int c) {
    std::cout << "a = " << a << ", b = " << b << ", c = " << c << std::endl;
}

void test_fun2(int a, long b, int c) {
    std::cout << "a = " << a << ", b = " << b << ", c = " << c << std::endl;
}

int main() {
    asyncscanf("1 2 3", test_fun);
    asyncscanf("1 2 3", test_fun2);
}

Примечания к коду:

  • идея в том, что у нас есть два пакета параметров: один для еще не проанализированных параметров и один для уже проанализированных параметров и параметры передаются по одному из первого пакета во второй; когда все параметры проанализированы, просто вызовите функцию
  • если вы забудете специализировать функцию синтаксического анализа для типа, необходимого функции, переданной в качестве третьего аргумента, компилятор asyncscanf сообщит вам об этом.
  • Мне пришлось использовать структуру шаблона asyncscanf_helper и кортежи вместо простого шаблона функции asyncscanf_helper из-за проблем с использованием двух пакетов параметров в одной функции шаблона.
  • Я использовал std::function в asyncscanf_helper, потому что он более общий, и вы можете использовать, например. lambdas в качестве ваших аргументов, но на данный момент я оставляю стандартную функцию в asyncscanf в качестве типа параметра, потому что в противном случае ее второй аргумент должен быть явно приведен к std::function с соответствующей подписью или параметры шаблона должны быть указаны явно.
  • Из-за фальшивой реализации специализаций функций синтаксического анализа вы не увидите ожидаемых результатов, если запустите код, но поскольку синтаксический анализ не был частью вашего вопроса, я оставил его высмеянным.
person robal    schedule 03.01.2015
comment
Небольшим улучшением будет включение ADL для parse путем замены типа template выводимым указателем на-T. Вы могли либо написать туда (инициализировано или нет), либо проигнорировать его и вернуть значение. - person Yakk - Adam Nevraumont; 04.01.2015