【问题标题】:Converting one std::optional to another std::optional将一个 std::optional 转换为另一个 std::optional
【发布时间】:2021-02-06 12:49:40
【问题描述】:

我有一个返回可选结构的方法,如下所示:

auto getBook(const std::string &title) const -> std::optional<Book>;

我想在另一个返回可选作者的方法中调用此方法。 问题是在调用方法之前,实现应该总是检查 getBook 返回的可选项是否被填写,像这样:

auto getAuthor(const std::string &title) const -> std::optional<Author>
{
   const auto optBook = getBook(title);
   if (optBook.has_value)
      return optBook->getAuthor();
   else
      return std::nullopt;
}

有没有办法用更短的方式来写这个,这样如果可选项被填充,则调用方法,但如果可选项为空,则返回std::nullopt。像这样的东西(我知道这目前不起作用,但你明白我的意思):

auto getAuthor(const std::string &title) const -> std::optional<Author>
{
   return getBook(title).getAuthor();
}

【问题讨论】:

  • C++ 中没有任何东西可以做到这一点。它不起作用的原因是由于 C++ 工作的核心、基本方式。如果你发现你必须经常做这种事情,你将不得不实现自己的脚手架来实现它,可能使用某种模板函数。
  • 此类用例的语法糖已在 Swift 和 Rust 等语言中引入,但尚未在 C++ 中引入(我相信我们不会很快看到这样的东西)

标签: c++ stdoptional


【解决方案1】:

您可以通过创建map 函数来概括此模式,该函数接受可选的o 和函数f,如果o.has_value() == true 则返回f(*o) 的结果:

template <typename O, typename F>
auto map(O&& o, F&& f) -> std::optional<decltype(f(*std::forward<O>(o)))>
{
    if (!o.has_value()) 
    {
        return {std::nullopt};
    }

    return {f(*std::forward<O>(o))};
}

然后您可以将getAuthor 定义为:

auto getAuthor(const std::string& title) -> std::optional<Author>
{
    return map(getBook(title), [](Book b)
    {
        return b.author;
    });
}

live example on godbolt.org


我为这类操作创建了一个库,名为scelta。使用我的库,您可以编写:

auto getBook(const std::string& title) -> std::optional<Book>;
auto getAuthor(const std::optional<Book>& title) -> std::optional<Author>;

using namespace scelta::infix;
std::optional<Author> a = getBook("...") | map(getAuthor);

请参阅"Monadic Optional Operations" 了解更多信息。

【讨论】:

    【解决方案2】:

    我认为您可以只返回您想要的内容并在根函数中捕获异常,如下所示:

    auto  get_size_t(const bool state)
    {
     return state ? std::optional<size_t>(std::rand()) : std::nullopt;
    }
    
    auto  get_string_size_t(const bool state)
    {
     return std::optional<std::string>(std::to_string(get_size_t(state).value()));
    }
    
    void  f()
    try
    {
     std::clog << get_string_size_t(true).value() << std::endl;
     std::clog << get_string_size_t(false).value() << std::endl;
    }
    catch (...)
    {
    }
    
    

    【讨论】:

    • 您不应该以这种方式使用异常处理。但如果你这样做,至少使用catch (const std::bad_optional_access&amp;) 而不是catch(...)
    • 我只是举一个我的解决方案的例子。
    【解决方案3】:

    boost::optional,这是 std::optional 的基础,有一个成员 map 执行此操作。

    例如

    auto getBook(const std::string &title) const -> boost::optional<Book>;
    
    auto getAuthor(const std::string &title) const -> boost::optional<Author>
    {
       return getBook(title).map(std::mem_fn(&Book::getAuthor));
    }
    

    【讨论】:

      【解决方案4】:

      我认为,这里需要Null Object 模式而不是 std::optional。

      是的,它有点像 std::optional ,但语义不同: 在对象上调用任何方法(如 getAuthor)时,如果方法的结果有效,则返回有效对象。如果不是,则返回 null 对象。

      不久前,我致力于一个类似 XML 的树实现,它是这样的:

      auto val = object.node("root").node("branch_1").node("subbranch_1").toInt();
      if (val) process(val.asInt());
      

      在 node() 调用的每一步中,如果未找到特定节点,则返回无效节点。在无效节点上调用时,返回无效节点。

      AFAIK,目前在 STL 中没有实现类似的功能。

      编辑:为了有效地工作,您需要从某个基类派生书籍、作者、章节和其他内容的所有实例,空对象将属于该类。

      编辑:这不是所有情况下的最佳解决方案,可能这太复杂而无法在您的情况下实施。

      【讨论】:

        猜你喜欢
        • 2021-06-10
        • 2018-04-07
        • 1970-01-01
        • 2014-11-21
        • 1970-01-01
        • 2021-06-06
        • 2021-01-18
        • 1970-01-01
        • 2021-05-16
        相关资源
        最近更新 更多