【问题标题】:Alternative to value_or that is indifferent to the returned type与返回类型无关的 value_or 的替代方案
【发布时间】:2018-11-16 08:23:54
【问题描述】:

我建立了一个小类Card 重载<< 运算符,基本上打印出卡片的花色和价值。

实现细节与我想在这里提出的问题无关,只是假设显而易见。对于Card,我建立了一个类CardDeck。当然,CardDeck 可能会用完卡。这促使我进行了这次尝试:

std::optional<Card> CardDeck::drawCard() {
    if (this->Cards.empty()) {
        return std::nullopt;
    }
    else {
        Card card = this->Cards.front();
        this->Cards.pop_front();
        return std::optional<Card>{card};
    }
}

现在可以抽一张牌,处理空牌的可能性是使用CardDeck 的客户端代码的责任,但该方法并不总是返回值是冗长的。我喜欢这个解决方案。

无论如何,我是 C++ 的新手,我做了一个天真的 ansatz:

std::cout&lt;&lt; cardDeck.drawCard().value_or("Out of cards!");

这会失败,因为 "Out of cards!" 的类型是 char* 但不是 Card

我的问题:有没有办法在不检查和访问 vaule/在两个单独的地方使用替换的情况下保护优雅的 oneliner?

【问题讨论】:

  • std::optional&lt;Card&gt; 重载operator&lt;&lt; 怎么样?
  • 或许提供免费功能std::ostream &amp; operator&lt;&lt;(std::ostream&amp;, const std::optional&lt;Card&gt;&amp;)
  • 类似my_optional_visit(cardDeck.drawCard(), [](const Card&amp;c){ std::cout &lt;&lt; c; }, [](){ std::cout &lt;&lt; "Out of Cards!" });
  • @Jarod42 你能详细说明一下吗?

标签: c++ stdoptional


【解决方案1】:

C++ 函数只能有一种返回类型。在您的情况下,您希望在运行时确定结果的类型,每次调用时它都可能不同。这是不可能的。

如果您需要经常这样做,我建议您只创建一个小型包装函数。

std::string optionalCardToString(const std::optional<Card> & card,
                                 const std::string & emptyString) {
    return card ? card->to_string() : emptyString;
}

std::cout << optionalCardToString(cardDeck.drawCard(), "Out of Cards");

如果性能是一个问题,您可以做很多事情来提高效率以避免复制,但总体思路就在那里。

我应该注意到,在现实世界的场景中,您不太可能从CarDeck 中绘制Card 并将其通过管道传输到cout。您通常还想对卡执行其他操作,例如将其添加到HandPile。这种需要做一些其他事情可以消除您对必须多次提及该值的担忧。

【讨论】:

    【解决方案2】:

    我会使用一个函数(命名空间范围或卡的静态成员),它接受 std::optional&lt;Card&gt; 并将其文本表示作为字符串返回。相关位是:

    class Card {
        ...
        std::string repr() const {
            std::string cr;
            // writes the textual representation of a card here
            return cr;
        }
        static std::string repr(const std::optional<Card> &card) {
            if (card.has_value()) {
                return card.value().repr();
            }
            return "Out of cards!";
        }
    };
    
    std::ostream& operator << (std ostream& out, const Card& card) {
        out << card.repr();
    }
    

    然后您只需使用:

    std::cout << Card::repr(cardDeck.drawCard());
    

    【讨论】:

      【解决方案3】:

      您可以为变体编写通用辅助函数,例如 std::visit,但对于 std::optional,类似:

      template <typename T, typename F, typename FEmpty>
      auto my_optional_visit(const std::optional<T>& o, F f, FEmpty fEmpty)
      {
          return o ? f(*o) : fEmpty();
      }
      

      然后:

      my_optional_visit(cardDeck.drawCard(),
                        [](const Card&c){ std::cout << c; },
                        [](){ std::cout << "Out of Cards!" });
      

      【讨论】:

        【解决方案4】:

        在我看来,处理这个问题的最简单方法是为operator &lt;&lt; 添加一个重载std::optional&lt;Card&gt;。在那里你可以处理打印内容的逻辑

        std::ostream& operator <<(std::ostream& os, const std::optional<Card>& card)
        {
            if (card == std::nullopt)
                return os << "Out of cards!";
            else
                return os << *card;
        }
        

        然后你就可以拥有

        std::cout << cardDeck.drawCard();
        

        【讨论】:

        • 我唯一担心的是失去灵活性。在更现代的语言中,字符串可以适应各自的情况。例如std::cout&lt;&lt;cardDeck.drawCard().value_or("Damn thief, give me my cards back!");.
        • @ThomasEberhard 我认为这将是对您的问题的有效补充,您希望文本依赖于上下文。
        • @ThomasEberhard 是的,你确实失去了一些灵活性。如果您想要该功能,那么我建议您使用 Jarod42 的答案或 Parker Coates 之类的答案。
        猜你喜欢
        • 2016-08-09
        • 1970-01-01
        • 1970-01-01
        • 2013-08-03
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-10-26
        • 1970-01-01
        相关资源
        最近更新 更多