LINUX.ORG.RU

Унифицированный вызов лямбды с различным количеством параметров

 ,


0

4

Проще объяснить сразу на примере того, что хочется добиться:

template <class Callback> void Manager::addHandler(const std::string& pattern, Callback handler)
{
//...???
}

void Manager::process(const std::string& request) 
{
//...как???
}

//... 
//...

manager.addHandler("dir/{0}", [](const std::string& dir) {
    //do smth
}); //1

manager.addHandler("root/{0}/{1}", [](const std::string& dir, const std::string& subdir) {
    //do smth other
}); //2

manager.process("root/kokoko/pokpok"); //должна вызваться лямбда, которую мы передали в //2 с аргументами "kokoko" и "pokpok"

Мы где то сохраняем паттерн, с которым сопоставляем строки (пути, запросы, команды, etc) и в соответствии с тем, с чем совпадет, вызовем сохраненную функцию обработчик с параметрами, выдранными из команды, с нужным количеством их. Есть какие идеи насчет реализации Manager::addHandler и Manager::process? Строка, передаваемая в process, может и будет генерироваться в рантайме



Последнее исправление: CatsCantFly (всего исправлений: 4)
Ответ на: комментарий от CatsCantFly

Думаю, можно и так. Нужно сделать шаблонных addHandler() вроде такого вот:

    template <class A1, class A2>
    void addHandler(const std::string& s, void (*f)(A1, A2))
    {
        // parse s
        // std::bind arguments to f;
        // store resulting void ()() function object
    }

asaw ★★★★★
()
Последнее исправление: asaw (всего исправлений: 1)
Ответ на: комментарий от asaw

Аргументы берутся в process из ее аргумента, когда функция уже должна быть где-то сохранена. В моем примере уже сохраненная ранее функция должна будет быть вызвана с агрументами «kokoko» и «pokpok». Поэтому с bind тут вариант не прокатит.

CatsCantFly
() автор топика
Ответ на: комментарий от CatsCantFly

Тогда нужно каким-то образом суметь в addHandler сгенерировать новую лямбду:

[](const std::vector& params) { handler(parms[0], parms[1]); }
Как это сделать, понятия не имею.

anatoly
()
Ответ на: комментарий от anatoly

Поэтому и спрашиваю тут:) Чувствую, что с помощью шаблонной магии, variadic templates и, может быть, boost::any (для хранения функций) это и можно сделать, но как - в голову не приходит.

CatsCantFly
() автор топика
Ответ на: комментарий от anatoly

Либо создать несколько addHandler с разным количеством параметров. Например, от 0 до 10.

template <class A1, class A2>
void addHandler(const std::string& s, const std::function<void(A1, A2)>& handler);
template <class A1, class A2, class A3>
void addHandler(const std::string& s, const std::function<void(A1, A2, A3)>& handler);
и т.д.

anatoly
()
Ответ на: комментарий от CatsCantFly

Если строка, которая передается первым аргументом в addHandler не известна на этапе компиляции, то, походу, вообще никак.

eao197 ★★★★★
()
Ответ на: комментарий от CatsCantFly

Поэтому с bind тут вариант не прокатит.

С каких пор это стало препятствием? Используй std::ref/std::cref в bind если надо.

asaw ★★★★★
()
Ответ на: комментарий от eao197

Известна. А вот та что передается в process - нет.

CatsCantFly
() автор топика

Вангую, такое можно сделать на spirit::qi.

Там точно можно вешать обработчики на заход в элемент дерева. Для оформления списка аргументов в компайл тайме посмотри на std::tuple и/или извраты из fusion.

Не соображу сходу - каков алгоритм матчинга в process в данном случае. Но по идее его можно инкапсулировать в корневое правило в грамматике, а в addHandler - как то создавать правило грамматики и вешать обработчик на заход в него.

В process - будет вызываться parse, для заданной (скомпонованной вызовами addHandler) грамматики.

Однако, тут надо держать в уме - что правила создавать можно, только на этапе компиляции, дальше только композиция.

Интуиция подсказывает, что этого достаточно будет, но сходу сообразить не выходит как это будет выглядеть.

pon4ik ★★★★★
()
Ответ на: комментарий от anatoly

Ну так ты же написал как. В чем вопрос? :o)

asaw ★★★★★
()
Ответ на: комментарий от anatoly
template <class A1, class A2>
void addHandler(const std::string& s, const std::function<void(A1, A2)>& handler)
{
__addHandler([](const vector<string>& params) { handler(params[0], params[1]); });
}

template <class A1, class A2, class A3>
void addHandler(const std::string& s, const std::function<void(A1, A2, A3)>& handler)
{
__addHandler([](const vector<string>& params) { handler(params[0], params[1], params[2]); });
}
anatoly
()
Ответ на: комментарий от CatsCantFly

неужели у тебя будут функции с количеством параметров более 10 ?

anatoly
()
Ответ на: комментарий от asaw

Я думаю, что основная проблема у CatsCantFly в том, что в process() парсинг строки будет проходить в run-time и только в run-time он будет узнавать, сколько же всего у него параметров. Соответственно, параметры он будет сохранять в vector (или что-то подобное) и возникнет проблема: как элементы вектора передавать параметрами в лямбды. А тут разве что тот путь, как показал anatoly выше.

Посредством sizeof можно было бы разве что в compile-time проверить, что количество параметров в строке соответствует количеству параметров в лямбде (речь про addHandler).

eao197 ★★★★★
()

Нужно сделать объект типа ~Context{...} в котором будут все необходимые поля для обработки запроса и лямбды должны, соответственно, принимать его как аргумент. Если нужны элементы паттерна, то нужно их засунуть через унифицированный интерфейс в контекст, например, как вектор чего-нибудь.

mashina ★★★★★
()
Последнее исправление: mashina (всего исправлений: 1)
Ответ на: комментарий от eao197

Соответственно, параметры он будет сохранять в vector (или что-то подобное) и возникнет проблема: как элементы вектора передавать параметрами в лямбды.

Можно как-то так (C++14):

#include <iostream>
#include <vector>
#include <string>
#include <utility>
#include <functional>

template<typename Vec, std::size_t... I, class... Args>
auto makeHandler(Vec& v, std::index_sequence<I...>, const std::function<void(Args&...)>& fo)
{
    return std::bind(fo, std::ref(v[I])...);
}

template<class... Args>
auto addHandler(std::vector<std::string>& vec, const std::string& /*s*/, const std::function<void(Args&...)>& fo)
{
    return makeHandler(vec, std::index_sequence_for<Args...>{}, fo);
}


int main()
{
    std::vector<std::string> vec {"a1", "a2", "a3", "a4"};
    auto fo = addHandler(vec, "", std::function<void(std::string&)>([](const std::string& s){ std::cout << s << std::endl; }));
    auto fo2 = addHandler(vec, "", std::function<void(std::string&, std::string&)>([](const std::string& s, const std::string& s2){ std::cout << s << ", " << s2 << std::endl; }));
    fo();
    vec[0] = "Hi there!";
    fo();
    fo2();

    return 0;
}
a1
Hi there!
Hi there!, a2

Единственное, от явных кастов вроде std::function<void(std::string&)>() не получается избавиться: лямбда это не std::function и без этого вывод шаблонных типов отказывается работать...

asaw ★★★★★
()
Последнее исправление: asaw (всего исправлений: 2)
Ответ на: комментарий от asaw

Аналог index_sequence и в C++11 делается. Но тут много чего не нравится. В первую очередь — это явное указание std::function<bla-bla-bla>.

eao197 ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.