Pull to refresh

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...)};
}
UFO just landed and posted this here
Sign up to leave a comment.