Kreator menu konsoli obsługujący tylko nagłówki
Jakiś czas temu napisałem tę odpowiedź na pytanie dotyczące tworzenia menu wiersza poleceń. Odniosłem się niedawno do tego i zauważyłem kilka rzeczy, które chciałem poprawić.
Cel, powód
Podobnie jak w przypadku wersji oryginalnej, celem jest posiadanie klasy, która upraszcza konstrukcję i używanie menu wiersza poleceń (konsoli).
Wprowadzone przeze mnie ulepszenia to:
- pozwalają albo
std::string
czystd::wstring
podpowiedzi i odpowiedzi - pozwalają użytkownikowi na oddzielenie selektorów od opisów
- przenieś wszystko do modułu tylko z nagłówkiem
- pozwalają na tworzenie
const
menu - sortuj według selektorów
pytania
Niektóre rzeczy, o które miałem pytania, to:
- nazwy parametrów szablonów - czy można je ulepszyć?
- użycie
default_in
idefault_out
- czy lepiej byłoby wywnioskować wartości domyślne z typu łańcucha? - wybór
std::function<void()>
jako operacja dla każdego wyboru - użycie
std::pair
a obiekt niestandardowy - czy powinienem to wszystko opakować w przestrzeń nazw?
- brakuje jakiejś funkcjonalności?
- czy jest sposób na wykonanie
constexpr
wersji?
menu.h
#ifndef MENU_H
#define MENU_H
#include <functional>
#include <iostream>
#include <map>
#include <string>
#include <utility>
template <typename T> struct default_in;
template<> struct default_in<std::istream> {
static std::istream& value() { return std::cin; }
};
template<> struct default_in<std::wistream> {
static std::wistream& value() { return std::wcin; }
};
template <typename T> struct default_out;
template<> struct default_out<std::ostream> {
static std::ostream& value() { return std::cout; }
};
template<> struct default_out<std::wostream> {
static std::wostream& value() { return std::wcout; }
};
template <class str, class intype, class outtype>
class ConsoleMenu {
public:
ConsoleMenu(const str& message,
const str& invalidChoiceMessage,
const str& prompt,
const str& delimiter,
const std::map<str, std::pair<str, std::function<void()>>>& commandsByChoice,
intype &in = default_in<intype>::value(),
outtype &out = default_out<outtype>::value());
void operator()() const;
private:
outtype& showPrompt() const;
str message;
str invalidChoiceMessage_;
str prompt;
str delimiter;
std::map<str, std::pair<str, std::function<void()>>> commandsByChoice_;
intype ∈
outtype &out;
};
template <class str, class intype, class outtype>
ConsoleMenu<str, intype, outtype>::ConsoleMenu(const str& message,
const str& invalidChoiceMessage,
const str& prompt,
const str& delimiter,
const std::map<str, std::pair<str, std::function<void()>>>& commandsByChoice,
intype &in, outtype& out) :
message{message},
invalidChoiceMessage_{invalidChoiceMessage},
prompt{prompt},
delimiter{delimiter},
commandsByChoice_{commandsByChoice},
in{in},
out{out}
{}
template <class str, class intype, class outtype>
outtype& ConsoleMenu<str, intype, outtype>::showPrompt() const {
out << message;
for (const auto &commandByChoice : commandsByChoice_) {
out << commandByChoice.first
<< delimiter
<< commandByChoice.second.first
<< '\n';
}
return out << prompt;
}
template <class str, class intype, class outtype>
void ConsoleMenu<str, intype, outtype>::operator()() const {
str userChoice;
const auto bad{commandsByChoice_.cend()};
auto result{bad};
out << '\n';
while (showPrompt() && (!(std::getline(in, userChoice)) ||
((result = commandsByChoice_.find(userChoice)) == bad))) {
out << '\n' << invalidChoiceMessage_;
}
result->second.second();
}
#endif // MENU_H
main.cpp
#include "menu.h"
#include <iostream>
#include <functional>
template <class str, class outtype>
class Silly {
public:
void say(str msg) {
default_out<outtype>::value() << msg << "!\n";
}
};
using MySilly = Silly<std::string, std::ostream>;
int main() {
bool running{true};
MySilly thing;
auto blabble{std::bind(&MySilly::say, thing, "BLABBLE")};
const ConsoleMenu<std::string, std::istream, std::ostream> menu{
"What should the program do?\n",
"That is not a valid choice.\n",
"> ",
". ",
{
{ "1", {"bleep", []{ std::cout << "BLEEP!\n"; }}},
{ "2", {"blip", [&thing]{ thing.say("BLIP"); }}},
{ "3", {"blorp", std::bind(&MySilly::say, thing, "BLORP")}},
{ "4", {"blabble", blabble }},
{ "5", {"speak Chinese", []{std::cout << "对不起,我不能那样做\n"; }}},
{ "0", {"quit", [&running]{ running = false; }}},
}
};
while (running) {
menu();
}
}
Pokazuje użycie programu i kilka różnych sposobów tworzenia funkcji menu. W zależności od ustawień konsoli i kompilatora chińskie zdanie może być wyświetlane poprawnie lub nie. Dalej jest wersja z szerokimi strunami.
wide.cpp
#include "menu.h"
#include <iostream>
#include <functional>
#include <locale>
template <class str, class outtype>
class Silly {
public:
void say(str msg) {
default_out<outtype>::value() << msg << "!\n";
}
};
using MySilly = Silly<std::wstring, std::wostream>;
int main() {
bool running{true};
MySilly thing;
auto blabble{std::bind(&MySilly::say, thing, L"BLABBLE")};
ConsoleMenu<std::wstring, std::wistream, std::wostream> menu{
L"What should the program do?\n",
L"That is not a valid choice.\n",
L"> ",
L". ",
{
{ L"1", {L"bleep", []{ std::wcout << L"BLEEP!\n"; }}},
{ L"2", {L"blip", [&thing]{ thing.say(L"BLIP"); }}},
{ L"3", {L"blorp", std::bind(&MySilly::say, thing, L"BLORP")}},
{ L"4", {L"blabble", blabble }},
{ L"5", {L"说中文", []{std::wcout << L"对不起,我不能那样做\n"; }}},
{ L"0", {L"quit", [&running]{ running = false; }}},
}
};
std::locale::global(std::locale{"en_US.UTF-8"});
while (running) {
menu();
}
}
Odpowiedzi
Odpowiedzi na Twoje pytania
nazwy parametrów szablonów - czy można je ulepszyć?
Głównie chodzi o to, że są niespójne. Nazwy typów rozpoczynaj od dużej litery i dodawaj do nich sufiks Type
lub nie. Sugeruję:
str
->Str
intype
->IStream
(żeby było jasne, spodziewamy się czegoś takiego jakstd::istream
tutaj)outtype
->OStream
użycie default_in i default_out - czy lepiej byłoby wywnioskować wartości domyślne z typu łańcucha?
Tak, patrz poniżej.
wybór
std::function<void()>
jako operacja dla każdego wyboru
Musisz std::function<>
tutaj zapisać funkcje dla każdego wyboru na mapie. Jedyne pytanie brzmi, czy void()
jest to właściwy typ funkcji. Jeśli chciałbyś operator()()
pobrać parametry i / lub zwrócić wartość, musiałbyś również zmienić typ funkcji.
użycie std :: pair a obiekt niestandardowy
Osobiście uważam, że to w porządku std::pair
.
czy powinienem to wszystko opakować w przestrzeń nazw?
Jeśli to jest sprawiedliwe class ConsoleMenu
, nie sądzę, by było to jakiekolwiek ulepszenie, aby umieścić go w przestrzeni nazw. Jednak umieściłbym default_in
i default_out
w przestrzeni nazw, ponieważ te nazwy są dość ogólne i nie chcesz, aby zanieczyszczały globalną przestrzeń nazw.
brakuje jakiejś funkcjonalności?
Nie wiem, czy to wszystko, czego potrzebujesz, to jest kompletne. Jeśli potrzebujesz czegoś innego, to nie jest.
czy istnieje sposób na stworzenie wersji constexpr?
Tak, upewniając się, że spełnia wymagania LiteralType . Oznacza to również, że wszystkie zmienne składowe muszą być poprawnymi LiteralTypes, co uniemożliwia użycie std::string
lub std::map
. Zamiast tego możesz użyć const char *
i std::array
.
Przekaż strumień wejściowy i wyjściowy według wartości
Konstrukcja, w której przekazujesz typ strumienia jako parametr szablonu, a następnie wyprowadzasz z tego konkretny strumień, jest bardzo dziwna, nieelastyczna i wymaga więcej wpisywania niż to konieczne. Po prostu dodaj strumień wejściowy i wyjściowy jako parametry do konstruktora:
template <class str, class intype, class outtype>
class ConsoleMenu {
public:
ConsoleMenu(const str& message,
...,
intype &in,
outtype &out);
Porównać:
ConsoleMenu<std::wstring, std::wistream, std::wostream> menu{...}
Przeciw:
ConsoleMenu<std::wstring> menu{..., std::wcin, std::wcout}
Jeśli chcesz, aby standardowe wejście i wyjście było parametrem domyślnym, wywnioskowałbym to z typu ciągu:
template <typename T> struct default_in;
template<> struct default_in<std::string> {
static std::istream& value() { return std::cin; }
};
template<> struct default_in<std::wstring> {
static std::wistream& value() { return std::wcin; }
};
...
template <class str, class intype, class outtype>
class ConsoleMenu {
public:
ConsoleMenu(const str& message,
...,
intype &in = default_in<str>::value(),
outtype &out = default_out<str>::value());
Bo wtedy możesz po prostu napisać:
ConsoleMenu menu{L"Wide menu", L"invalid", L"> ", L". ", {/* choices */}};