Comments 5
Самый красивый сахар для получения значения из variant - visit https://en.cppreference.com/w/cpp/utility/variant/visit
При использовании простенького хелпера overloaded можно получить:
std::visit(overloaded {
[](auto arg) { std::cout << arg << ' '; },
[](double arg) { std::cout << std::fixed << arg << ' '; },
[](const std::string& arg) { std::cout << std::quoted(arg) << ' '; }
}, v);
Видел, знаю, практикую, но если вы про реализацию через std::variant
task coroutine(channel<int>::out out1, channel<std::string>::out out2) {
auto result = co_await select(out1, out2);
if (auto *v = std::get_if<int>(&result); v != nullptr) {
std::cout << "got int " << *v << std::endl;
} else if (auto *v = std::get_if<std::string>(&result); v != nullptr) {
std::cout << "got string " << *v << std::endl;
}
}
Как вы обработаете однотипные каналы? std::variant позволяет хранить один и тот же тип в нескольких разных позициях одновременно, но вот сделать для них различную обработку вашим способом уже не получится. Кроме того, в вашем примере куча лямбд - читайте мои сноски, там описано чем это вам грозит. Мой же вариант с лямбдами будет интереснее, потому что вам не надо отдельно перечислять прослушиваемые каналы, а отдельно их обрабатывать (один источник истины).
Ваш пример выглядит красиво, но в данном случае собирает в себе худшие свойства обоих приведенных мной.
Да, согласен, с однотипными каналами ничего не получится. Мне хотелось спасти variant, сделав его таким же красивым как лямбды.
В вашем подходе с лямбдами слегка напрягает использование оператора >>. Имхо, его и в std для потоков не к месту использовали, и продолжать это - плохая идея. Изобретение своего языка внутри C++ начинается. Может быть просто передать по порядку: канал, лямбда, канал, лямбда? Fold expressions в лоб такое не переварят, но подумать можно.
P.S. Забыл в начале поблагодарить. Большое спасибо за пост, очень качественно и полезно.
Можно применив еще некоторое количество шаблонной магии добиться такого:
task coroutine(channel<int>::out out1, channel<int>::out out2, channel<std::string>::out out3) {
auto result = co_await select(out1, out2, out3);
if (auto *value = result.get_if(out1); value != nullptr) {
std::cout << "got int " << *value << std::endl;
} else if (auto *value = result.get_if(out2); value != nullptr) {
std::cout << "got int2 " << *value << std::endl;
} else if (auto *value = result.get_if(out3); value != nullptr) {
std::cout << "got string " << *value << std::endl;
}
}
У меня получилась следующая реализация.
using channel_id = void *;
template <typename Type>
class channel {
// ...
class out {
// ...
channel_id id() const noexcept { return reinterpret_cast<channel_id>(m_self); }
};
};
// unique_variant_t я просто нагуглил, но вроде работает
template <typename T, typename... Ts>
struct filter_duplicates { using type = T; };
template <template <typename...> class C, typename... Ts, typename U, typename... Us>
struct filter_duplicates<C<Ts...>, U, Us...>
: std::conditional_t<(std::is_same_v<U, Ts> || ...)
, filter_duplicates<C<Ts...>, Us...>
, filter_duplicates<C<Ts..., U>, Us...>> {};
template <typename T>
struct unique_variant;
template <typename... Ts>
struct unique_variant<std::variant<Ts...>> : filter_duplicates<std::variant<>, Ts...> {};
template <typename T>
using unique_variant_t = typename unique_variant<T>::type;
// получить значение из результата по ссылке на канал
// замена std::get_if
template <typename ...Channels>
struct select_result {
unique_variant_t<std::variant<typename Channels::value_type ...>> value;
channel_id channel;
template <channel_out Channel>
auto get_if(const Channel &c) -> typename Channel::value_type * {
if (c.id() == channel) {
return &std::get<typename Channel::value_type>(value);
}
return nullptr;
}
};
template <channel_out ...Channels>
auto select(Channels ...channels) {
struct [[nodiscard]] awaitable {
using handle = std::experimental::coroutine_handle<>;
std::tuple<Channels...> channels;
std::shared_ptr<handle> coro;
bool await_ready() const {
return std::apply([](auto &...values) {
return (values.await_ready() || ...);
}, channels);
}
void await_suspend(handle c) {
coro = std::make_shared<handle>(c);
std::apply([this](auto &...values) {
(values._suspend(coro), ...);
}, channels);
}
auto await_resume() {
select_result<Channels...> result;
std::apply([&result](auto & ...values) {
return ([&result] <typename Type> (Type &value) {
if (value.await_ready()) {
result.value.template emplace<typename Type::value_type>(value.await_resume());
result.channel = value.id();
return true;
}
return false;
}(values) || ...);
}, channels);
return std::move(result);
}
};
return awaitable{std::make_tuple(channels...)};
}
Каналы на корутинах С++