与返回类型无关的 value_or 的替代方案

Alternative to value_or that is indifferent to the returned type

我构建了一些 class Card 重载 << 运算符,主要是打印花色和牌面值。

实现细节与我想在这里问的问题无关,只是假设显而易见。对于 Card 我建了一个 class 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 的客户端代码的责任,但是该方法并不总是 return 一个值是冗长的.我喜欢这个解决方案。

无论如何,作为 C++ 的新手,我做出了天真的回答:

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

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

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

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

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();

您可以为变体编写一个 la 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!" });

我会使用一个函数(命名空间作用域或卡的静态成员)将 std::optional<Card> 和 return 的文本表示形式作为字符串。相关位将是:

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());

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

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

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。这需要做一些其他事情来消除您对不得不多次提及该值的担忧。