Я построил маленький класс 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
,
Мой вопрос: есть ли способ сохранить элегантный вкладыш без проверки и доступа к вауле / с помощью замены в двух отдельных местах?
Самый простой способ справиться с этим в моем мнении — добавить перегрузку для 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
, Это нужно сделать что-то еще, что снимает с вас беспокойство о необходимости упоминать стоимость более одного раза.