Créateur de menu de console générique uniquement en-tête
Il y a quelque temps, j'ai écrit cette réponse à une question sur la création d'un menu en ligne de commande. J'y ai fait allusion récemment et j'ai remarqué certaines choses que je voulais améliorer.
Objectif
Comme pour la version originale, le but est d'avoir une classe qui simplifie la construction et l'utilisation d'un menu en ligne de commande (console).
Les améliorations que j'ai apportées sont:
- permettre soit
std::string
oustd::wstring
invites et réponses - permettre à l'utilisateur de séparer les sélecteurs des descriptions
- tout déplacer dans un module d'en-tête uniquement
- permettre la création de
const
menus - trier par sélecteurs
Des questions
Certaines choses sur lesquelles j'avais des questions sont:
- noms de paramètres de modèle - pourraient-ils être améliorés?
- utilisation de
default_in
etdefault_out
- serait-il préférable de déduire les valeurs par défaut du type chaîne? - choix de
std::function<void()>
l'opération pour chaque choix - utilisation de
std::pair
vs objet personnalisé - dois-je envelopper tout cela dans un espace de noms?
- une fonctionnalité manque-t-elle?
- existe-t-il un moyen de créer une
constexpr
version?
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();
}
}
Cela montre l'utilisation du programme et plusieurs façons différentes de créer des fonctions de menu. Selon les paramètres de votre console et de votre compilateur, la phrase chinoise peut ou non s'afficher correctement. Vient ensuite une version à chaîne large.
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();
}
}
Réponses
Réponses à vos questions
noms de paramètres de modèle - pourraient-ils être améliorés?
La plupart du temps, c'est qu'ils sont incohérents. Commencez les noms de type par une majuscule et ajoutez-les tous Type
ou non. Je suggère:
str
->Str
intype
->IStream
(juste pour être clair, nous nous attendons à quelque chose commestd::istream
ici)outtype
->OStream
utilisation de default_in et default_out - serait-il préférable de déduire les valeurs par défaut à partir du type de chaîne?
Oui, voir ci-dessous.
choix de
std::function<void()>
l'opération pour chaque choix
Vous devez std::function<>
ici stocker les fonctions pour chaque choix dans la carte. La seule question est de savoir si void()
est le bon type pour la fonction. Si vous vouliez operator()()
prendre des paramètres et / ou renvoyer une valeur, vous devrez également changer le type de la fonction.
utilisation de std :: pair vs objet personnalisé
Personnellement, je pense que ça va std::pair
.
dois-je envelopper tout cela dans un espace de noms?
Si c'est juste class ConsoleMenu
, je ne pense pas que ce serait une amélioration de le mettre dans un espace de noms. Cependant, je mettrais default_in
et default_out
dans un espace de noms, car ces noms sont assez génériques et vous ne voulez pas qu'ils polluent l'espace de noms global.
une fonctionnalité manque-t-elle?
Je ne sais pas, si c'est tout ce dont vous avez besoin, alors c'est complet. Si vous avez besoin de quelque chose d'autre, ce n'est pas le cas.
existe-t-il un moyen de créer une version constexpr?
Oui, en vous assurant qu'il répond aux exigences de LiteralType . Cela signifie également que toutes les variables membres doivent être des LiteralTypes valides, ce qui empêche l'utilisation de std::string
ou std::map
. Vous pouvez utiliser const char *
et à la std::array
place.
Passer le flux d'entrée et de sortie par valeur
La construction que vous avez dans laquelle vous passez un type de flux en tant que paramètre de modèle, puis faites en déduire un flux concret est très étrange, inflexible et nécessite plus de frappe que nécessaire. Ajoutez simplement le flux d'entrée et de sortie en tant que paramètres au constructeur:
template <class str, class intype, class outtype>
class ConsoleMenu {
public:
ConsoleMenu(const str& message,
...,
intype &in,
outtype &out);
Comparer:
ConsoleMenu<std::wstring, std::wistream, std::wostream> menu{...}
Contre:
ConsoleMenu<std::wstring> menu{..., std::wcin, std::wcout}
Si vous voulez que l'entrée et la sortie standard soient un paramètre par défaut, je le déduire du type de chaîne:
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());
Parce qu'alors, vous pouvez simplement écrire:
ConsoleMenu menu{L"Wide menu", L"invalid", L"> ", L". ", {/* choices */}};