Альтернатива value_or, которая безразлична к возвращаемому типу

Я построил маленький класс 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++ я сделал наивный анзац:

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

Это терпит неудачу как тип "Out of cards!" является char* но нет Card,

Мой вопрос: есть ли способ сохранить элегантный вкладыш без проверки и доступа к вауле / с использованием замены в двух отдельных местах?

4 ответа

Решение

Самый простой способ справиться с этим в моем мнении - добавить перегрузку для 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();

Вы могли бы написать универсальную вспомогательную функцию а-ля 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> и вернуть его текстовое представление в виде строки. Соответствующие биты будут:

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++ может иметь только один тип возвращаемого значения. В вашем случае вы бы хотели, чтобы тип результата определялся во время выполнения, где он может отличаться при каждом вызове. Это просто невозможно.

Если вам нужно делать это достаточно часто, я бы предложил создать небольшую функцию-обертку.

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

Если производительность является проблемой, вы можете сделать многое, чтобы сделать ее более эффективной, чтобы избежать копирования, но общая идея есть.

Я должен отметить, что в реальном сценарии вы вряд ли Card от CarDeck и просто трубку cout, Как правило, вы захотите сделать что-то еще с картой, например, добавить ее в Hand или Pile, Это нужно сделать что-то еще, что снимает с вас беспокойство о необходимости упоминать стоимость более одного раза.

Другие вопросы по тегам