Как проверить действительный Boost Asio CompletionToken?

Я пишу асинхронную функцию в стиле Boost.Asio, которая принимает CompletionToken аргумент. Аргумент может быть функцией, объектом функции, лямбда-выражением, будущим, ожидаемым и т. д.

CompletionToken — параметр шаблона. Если я не ограничу аргумент, функция может соответствовать неожиданному параметру, например int. Поэтому я хочу написать какое-то ограничение, используя std::enable_if. (Моя среда - С++ 17).

Если CompletionToken принимает параметры, то я могу использовать std::is_invocable для проверки.

См. f1 в моем примере кода.

Однако у меня возникла проблема. Если CompletionToken не принимает параметров, то boost::asio::use_future совершает ошибку.

См. f2 в моем примере кода.

После некоторых попыток и ошибок я получил свое решение. Это объединение std::is_invocable и use_future_t проверки по ИЛИ (||).

См. f3 в моем примере кода.

Но это не так элегантно. Кроме того, я не уверен, что Boost.Asio поддерживает другие функции, например, use_awaitable_t требует аналогичной проверки прямого соответствия.

Я пытался найти Boost.Asio, предоставляющий признаки типа или предикат, например is_completion_token, но не смог его найти.

Есть ли лучший способ проверить CompletionToken?

Ссылка на Godbolt https://godbolt.org/z/sPeMo1GEK

Полный код:

#include <type_traits>
#include <boost/asio.hpp>

// Callable T takes one argument
template <
    typename T,
    std::enable_if_t<std::is_invocable_v<T, int>>* = nullptr
>
void f1(T) {
}

// Callable T takes no argument
template <
    typename T,
    std::enable_if_t<std::is_invocable_v<T>>* = nullptr
>
void f2(T) {
}


template <template <typename...> typename, typename>
struct is_instance_of : std::false_type {};

template <template <typename...> typename T, typename U>
struct is_instance_of<T, T<U>> : std::true_type {};

// Callable T takes no argument
template <
    typename T,
    std::enable_if_t<
        std::is_invocable_v<T> ||
        is_instance_of<boost::asio::use_future_t, T>::value
    >* = nullptr
>
void f3(T) {
}

int main() {
    // no error
    f1([](int){});
    f1(boost::asio::use_future);

    // same rule as f1 but use_future got compile error
    f2([](){});
    f2(boost::asio::use_future); // error

    // a little complecated typechecking, then no error
    f3([](){});
    f3(boost::asio::use_future);
}

Выходы:

Output of x86-64 clang 13.0.1 (Compiler #1)
<source>:45:5: error: no matching function for call to 'f2'
    f2(boost::asio::use_future); // error
    ^~
<source>:17:6: note: candidate template ignored: requirement 'std::is_invocable_v<boost::asio::use_future_t<std::allocator<void>>>' was not satisfied [with T = boost::asio::use_future_t<>]
void f2(T) {
     ^
1 error generated.
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
0
53
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Если у вас есть концепции С++ 20, посмотрите ниже. В противном случае читайте дальше.

Если вы хотите правильно реализовать протокол асинхронных результатов с помощью Asio, вы должны использовать трейт async_result или async_initiate, как описано здесь.

Это должен быть надежный ключ для SFINAE. Аргументы шаблона для async_result включают токен и подпись(и) завершения:

Live On Compiler Explorer

#include <boost/asio.hpp>
#include <iostream>
using boost::asio::async_result;

template <typename Token,
          typename R = typename async_result<std::decay_t<Token>, void(int)>::return_type>
void f1(Token&&) {
    std::cout << __PRETTY_FUNCTION__ << "\n";
}

template <typename Token,
          typename R = typename async_result<std::decay_t<Token>, void()>::return_type>
void f2(Token&&) {
    std::cout << __PRETTY_FUNCTION__ << "\n";
}

int main() {
    auto cb1 = [](int) {};
    f1(cb1);
    f1(boost::asio::use_future);
    f1(boost::asio::use_awaitable);
    f1(boost::asio::detached);
    f1(boost::asio::as_tuple(boost::asio::use_awaitable));

    auto cb2 = []() {};
    f2(cb2);
    f2(boost::asio::use_future);
    f2(boost::asio::use_awaitable);
    f2(boost::asio::detached);
    f2(boost::asio::as_tuple(boost::asio::use_awaitable));
}

Уже печатает

void f1(Token&&) [with Token = main()::<lambda(int)>&; R = void]
void f1(Token&&) [with Token = const boost::asio::use_future_t<>&; R = std::future<int>]
void f1(Token&&) [with Token = const boost::asio::use_awaitable_t<>&; R = boost::asio::awaitable<int, boost::asio::any_io_executor>]
void f1(Token&&) [with Token = const boost::asio::detached_t&; R = void]
void f1(Token&&) [with Token = boost::asio::as_tuple_t<boost::asio::use_awaitable_t<> >; R = boost::asio::awaitable<std::tuple<int>, boost::asio::any_io_executor>]
void f2(Token&&) [with Token = main()::<lambda()>&; R = void]
void f2(Token&&) [with Token = const boost::asio::use_future_t<>&; R = std::future<void>]
void f2(Token&&) [with Token = const boost::asio::use_awaitable_t<>&; R = boost::asio::awaitable<void, boost::asio::any_io_executor>]
void f2(Token&&) [with Token = const boost::asio::detached_t&; R = void]
void f2(Token&&) [with Token = boost::asio::as_tuple_t<boost::asio::use_awaitable_t<> >; R = boost::asio::awaitable<std::tuple<>, boost::asio::any_io_executor>]

С++ 20 концепций

Теперь имейте в виду, что приведенное выше является «слишком слабым» из-за частичного создания экземпляра шаблона. Некоторые части async_result на самом деле не используются. Это означает, что f2(cb1);на самом деле скомпилируется.

Связанные документы даже включают концепцию C++20 completion_token_for<Sig>, которая позволяет вам быть точным без усилий: Live On Compiler Explorer

template <boost::asio::completion_token_for<void(int)> Token> void f1(Token&&) {
    std::cout << __PRETTY_FUNCTION__ << "\n";
}

template <boost::asio::completion_token_for<void()> Token> void f2(Token&&) {
    std::cout << __PRETTY_FUNCTION__ << "\n";
}

В противном случае

На практике вы всегда будете следовать рецепту Asio, и это гарантирует использование всех частей. Помимо примера в документации, вы можете искать существующие ответы

Пример:

template <typename Token>
typename asio::async_result<std::decay_t<Token>, void(error_code, int)>::return_type
async_f1(Token&& token) {
    auto init = [](auto completion) {
        auto timer =
            std::make_shared<asio::steady_timer>(boost::asio::system_executor{}, 1s);
        std::thread(
            [timer](auto completion) {
                error_code ec;
                timer->wait(ec);
                std::move(completion)(ec, 42);
            },
            std::move(completion))
            .detach();
    };

    return asio::async_result<std::decay_t<Token>, void(error_code, int)>::initiate(
        init, std::forward<Token>(token));
}

Спасибо. Когда я создаю async_result<...>::initiate(...) в функции f1() и f2() и передаю неверный токен, такой как int, я получаю сообщение об ошибке /usr/include/boost/asio/compose.hpp:380:5: error: called object type 'int' is not a function or function pointer. Я могу получить точку вызова из журнала ошибок (например, обратная трассировка). Это не так сложно, так что это приемлемое решение. В моем реальном коде используется async_compose...

Takatoshi Kondo 15.11.2022 06:48

...Это похоже на async_result<>::initiate. Но ошибка происходит немного глубже в библиотеке asio. Я ищу способ сделать ошибку в f1() и f2(), используя SFINAE. Я добавил недопустимый токен в ваш пример compiler-explorer.com/z/KT57hzK3o , но об ошибке не сообщается. Если бы я мог написать хороший SFINAE в f1() и f2() точке, было бы здорово. Что вы думаете?

Takatoshi Kondo 15.11.2022 06:48

Я предполагаю, что Boost.Asio не предоставляет способа проверки проверки токена для асинхронных функций пользователя, таких как токен аргумента f1() и f2(). Но их реализация обычно использует async_result<>::initiate или async_compose. И они проверяют токен, и если пользователь передал неверный токен, сообщается об ошибке. Ошибка достаточно проста для понимания и легко находит точку вызова. Поэтому Boost.Asio считает, что проверка f1() и f2() не нужна. Это мое понимание, пока.

Takatoshi Kondo 15.11.2022 07:26

Извините, я что-то неправильно понимаю. Только когда я использую asio::async_result<...)>::return_type в качестве возвращаемого типа, точка вызова включается в журнал ошибок компиляции. Если я заменил его на auto, что означает отсутствие проверки, то точка вызова исчезла из журнала ошибок компиляции. Поэтому использование asio::async_result<...)>::return_type — это решение, которое я хочу знать. Еще раз спасибо.

Takatoshi Kondo 15.11.2022 07:37

«Я предполагаю, что Boost.Asio не дает возможности проверить валидацию токена» — что не так с asio::completion_token_for? Я думаю, вы, вероятно, сможете найти удобную для С++ 17 реализацию (деталь), которую вы можете использовать повторно.

sehe 15.11.2022 13:49

Об использовании return_type - да, это то, о чем я предупреждал в ответе ""слишком слабый" из-за частичного создания экземпляра шаблона. Некоторые части async_result на самом деле не используются". Имейте в виду, вам не нужно беспокоиться, поскольку async_compose по определению создает экземпляр всего класса.

sehe 15.11.2022 13:50

Я не проверял реализацию asio::completion_token_for, потому что моя среда не C++20, а C++17. Я посмотрю и попробую портировать реализацию C++17. Возможно, может и не получиться. Просто я предполагаю, что если есть способ реализовать asio::completion_token_for совместимые с SFINAE дружественные type_traits , Boost.Asio предоставит его как часть библиотеки. В любом случае попробую.

Takatoshi Kondo 15.11.2022 13:58

Я не понимаю, зачем они это предоставляют, потому что это не добавляет ценности. Причина, по которой концепция добавляет ценность, заключается именно в том, что она может привести к более четким сообщениям об ошибках. Если вы можете явно принудительно выполнить ранние проверки, вы всегда можете превратить это в static_assert, что сделает его «более простым» для ваших пользователей (так что вместо SFINAE, что приводит к... сложной диагностике компилятора). Имейте в виду, что диагностика для C++17 всегда будет сложной, но потенциально вы можете добавить static_assert в «помощь».

sehe 15.11.2022 14:09

Давайте продолжим обсуждение в чате.

Takatoshi Kondo 16.11.2022 01:27

Другие вопросы по теме