Реализация функции промежуточного программного обеспечения с использованием функций-членов с помощью lambdas / bind - PullRequest
0 голосов
/ 22 ноября 2018

У меня есть функция Post(), которая принимает два аргумента - путь std::string для прослушивания запросов и std::function<void(Request &, Response &)> для обработки входящего запроса.Обратите внимание, что я не могу изменить Post().

Например:

m_server.Post("/wallet/open", [this](auto req, auto res)
{
    std::cout << req.body << std::endl;
}

Я пытаюсь передать запрос через функцию промежуточного программного обеспечения, а затем перейти к функции обработчика.,

Функция обработчика и функция промежуточного программного обеспечения являются функциями-членами.Настройка привязки Post () происходит внутри функции-члена того же класса.

Это работает:

m_server.Post("/wallet/open", [this](auto req, auto res){
    auto f = std::bind(&ApiDispatcher::openWallet, this, _1, _2);
    middleware(req, res, f);
});

Однако я подключаю разумное количество запросов,и было бы неплохо абстрагировать это в функцию, поэтому мы можем просто вызвать, например,

m_server.Post("/wallet/open", router(openWallet));

Мне удалось получить что-то подобное, но я не могу понять,Как заставить это работать при использовании функций-членов.Это прекрасно работает, если все является бесплатной функцией:

const auto router = [](auto function)
{
    return std::bind(middleware, _1, _2, function);
};

m_server.Post("/wallet/open", router(openWallet))
        .Post("/wallet/keyimport", router(keyImportWallet))
        .Post("/wallet/seedimport", router(seedImportWallet))
        .Post("/wallet/viewkeyimport", router(importViewWallet))
        .Post("/wallet/create", router(createWallet))

Однако моя попытка заставить ее работать для функций-членов не работает, и я не могу понять, что говорит мне сообщение об ошибке.:

const auto router = [](auto function)
{
    return std::bind(&ApiDispatcher::middleware, _1, _2, function);
};

m_server.Post("/wallet/open", router(&ApiDispatcher::openWallet))
        .Post("/wallet/keyimport", router(&ApiDispatcher::keyImportWallet))
        .Post("/wallet/seedimport", router(&ApiDispatcher::seedImportWallet))
        .Post("/wallet/viewkeyimport", router(&ApiDispatcher::importViewWallet))
        .Post("/wallet/create", router(&ApiDispatcher::createWallet))

Функция роутера сама по себе работает.Я думаю, проблема в том, что я не вызываю функции обработчика на this.Я попытался сделать это вместо этого:

m_server.Post("/wallet/open", router(std::bind(&ApiDispatcher::openWallet, this, _1, _2)));

Но затем я получаю сообщение об ошибке "Неверное количество аргументов для указателя на член".

Вот минимальный пример для репликации проблемы:

#include <iostream>
#include <string>
#include <functional>

using namespace std::placeholders;

class ApiDispatcher
{
    public:
        void middleware(std::string req, std::string res, std::function<void(std::string req, std::string res)> handler)
        {
            // do some processing
            handler(req + " - from the middleware", res);
        }

        void dispatch()
        {
            const auto router = [](auto function)
            {
                return std::bind(&ApiDispatcher::middleware, _1, _2, function);
            };

            /* Uncommenting this line breaks compilation */
            // Post("/wallet/open", router(&ApiDispatcher::openWallet));
        }

        void openWallet(std::string req, std::string res)
        {
            std::cout << req << std::endl;
        }

        void Post(std::string method, std::function<void(std::string req, std::string res)> f)
        {
            // wait for a http request
            f("request", "response");
        }    
};

int main()
{
    ApiDispatcher server;
    server.dispatch();
}

Спасибо.Извините, пост был таким длинным.

Ответы [ 4 ]

0 голосов
/ 22 ноября 2018

Корень проблемы в том, что параметр в router лямбде и как std::bind внутри лямбды будет интерпретировать его.

давайте предположим, что вы исправили вызов router внутри dispatch, заменив:

Post("/wallet/open", router(&ApiDispatcher::openWallet));

соответствующей формой:

Post("/wallet/open", router(std::bind(&ApiDispatcher::openWallet, this, _1, _2)));

, затем cppreference , параметр function лямбдыrouter будет выведено как выражение привязки, и, как следствие, std::bind внутри router увидит, что этот параметр удовлетворяет std::is_bind_expression<T>::value == true, и при вызове middleware не будет предприниматься попытка преобразования в std::function.

Чтобы исправить это, вам нужно явно указать тип параметра function в router lambda, например:

const auto router = [this](std::function<void(std::string req, std::string res)> function)
{
    return std::bind(&ApiDispatcher::middleware, this, _1, _2, function);
};

Делая это таким образом, неявное преобразование

std::bind(&ApiDispatcher::openWallet, this, _1, _2)

до std::function происходит перед вызовом

std::bind(&ApiDispatcher::middleware, this, _1, _2, function)

или, в качестве альтернативы, вы сохраняете auto и запускаете неявное преобразование внутри лямбды:

const auto router = [this](auto function)
{
    std::function<void(std::string req, std::string res)> func = function;
    return std::bind(&ApiDispatcher::middleware, this, _1, _2, func);
};
0 голосов
/ 22 ноября 2018

Мне проще рассуждать с лямбдами, и, насколько я понимаю, они предпочтительнее, чем std::bind из соображений производительности.

void dispatch()
{
    Post("/wallet/open", [&](std::string req_1, std::string res_1){
        middleware(req_1, res_1, [&](std::string req_2, std::string res_2){
            openWallet(req_2, res_2);
        });
    });
}

Каждый вызов функции-члена переносится в лямбдуВот.Вам, вероятно, следует использовать const std::string& в качестве аргументов, чтобы избежать копий для всех вызовов.

Еще одна вещь, которая должна указывать на производительность.Пока вы только переадресовываете вызовы функций (вы не сохраняете вызываемый объект для последующего использования), вы можете изменить middleware и Post на шаблоны.Вызываемым может быть параметр шаблона, и вам не придется платить накладные расходы на std::function.

template <typename F>
void middleware(std::string req, std::string res, F handler)
{
    // do some processing
    handler(req + " - from the middleware", res);
}

template <typename F>
void Post(std::string method, F f)
{
    // wait for a http request
    f("request", "response");
}

Edit

Чтобы извлечь часть логики вкак я думаю, вы имеете в виду, вы передаете указатель на член-функцию.

void dispatch()
{
    auto const router = [&](auto m_fptr) {
        return [&, m_fptr](std::string req, std::string res) {
            middleware(req, res, [&](std::string req_2, std::string res_2) {
                (this->*m_fptr)(req_2, res_2);
            });
        };
    };

    Post("/wallet/open", router(&ApiDispatcher::openWallet));
}
0 голосов
/ 22 ноября 2018

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

    const auto router = [this](auto function)
    {
        std::function<void(std::string req, std::string res)> fn
           = std::bind(function, this, _1, _2);
        return std::bind(&ApiDispatcher::middleware, this, _1, _2, fn);
    };

Но я предлагаю придерживаться решения lambda-inside-lambda, предложенного super.

0 голосов
/ 22 ноября 2018

Ваша функция маршрутизатора должна возвращать функцию, которая будет вызывать указанную функцию-член с определенным экземпляром класса.

void dispatch()
{
    const auto router = [this](auto function) {
        return std::bind(function, this, _1, _2);
    };

    Post("/wallet/open", router(&ApiDispatcher::openWallet));
}

Или, и я пропустил это изначально, чтобы маршрутизировать все через функцию промежуточного программного обеспеченияСамым простым, что я смог найти, было.

void dispatch()
{
    const auto router = [this](auto function) {
        return [this, function](auto a, auto b) {
            middleware(a, b, std::bind(function, this, _1, _2));
        };
    };

    Post("/wallet/open", router(&ApiDispatcher::openWallet));
}

Или, если вы предпочитаете использовать связывание, вам сначала нужно заставить внутреннюю привязку быть функцией, и выполните.

void dispatch()
{
    const auto router = [this](auto function) {
        std::function<void(std::string, std::string)> func = std::bind(function, this, _1, _2);
        return std::bind(&ApiDispatcher::middleware, this, _1, _2, func);
    };

    Post("/wallet/open", router(&ApiDispatcher::openWallet));
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...