Конструктор универсального меню консоли только для заголовков
Некоторое время назад я написал этот ответ на вопрос о создании меню командной строки. Я недавно упомянул об этом и заметил несколько вещей, которые хотел улучшить.
Цель
Как и в исходной версии, целью является создание класса, который упрощает создание и использование меню командной строки (консоли).
Я сделал следующие улучшения:
- разрешить либо подсказки,
std::string
либоstd::wstring
ответы - позволяют пользователю отделять селекторы от описаний
- переместить все в модуль только для заголовков
- разрешить создание
const
меню - сортировать по селекторам
Вопросы
У меня возникли вопросы:
- имена параметров шаблона - можно ли их улучшить?
- использование
default_in
иdefault_out
- не лучше ли вывести значения по умолчанию из строкового типа? - выбор в
std::function<void()>
качестве операции для каждого выбора - использование по
std::pair
сравнению с настраиваемым объектом - я должен обернуть все это в пространство имен?
- отсутствует какая-либо функциональность?
- есть способ сделать
constexpr
версию?
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();
}
}
Здесь показано использование программы и несколько различных способов создания функций меню. В зависимости от настроек вашей консоли и компилятора предложение на китайском языке может отображаться или отображаться неправильно. Далее идет версия с широкой строкой.
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();
}
}
Ответы
Ответы на ваши вопросы
имена параметров шаблона - можно ли их улучшить?
В основном они непоследовательны. Начинайте имена с заглавной буквы и либо добавляйте к ним суффикс, Type
либо нет. Я предлагаю:
str
->Str
intype
->IStream
(просто чтобы прояснить, мы действительно ожидаем чего-то вродеstd::istream
здесь)outtype
->OStream
использование default_in и default_out - не лучше ли вывести значения по умолчанию из строкового типа?
Да, смотрите ниже.
выбор в
std::function<void()>
качестве операции для каждого выбора
Здесь вам нужно std::function<>
сохранить функции для каждого выбора на карте. Вопрос только в том, подходит ли void()
этот тип функции. Если вы хотите operator()()
принимать параметры и / или возвращать значение, вам также придется изменить тип функции.
использование std :: pair против настраиваемого объекта
Я лично считаю, что это нормально std::pair
.
я должен обернуть все это в пространство имен?
Если это справедливо class ConsoleMenu
, я не думаю, что было бы лучше поместить его в пространство имен. Однако я бы поместил default_in
и default_out
в пространство имен, так как эти имена довольно общие, и вы не хотите, чтобы они загрязняли глобальное пространство имен.
отсутствует какая-либо функциональность?
Не знаю, если это все, что вам нужно, значит, все готово. Если вам нужно что-то еще от него, это не так.
есть ли способ сделать версию constexpr?
Да, убедившись, что он удовлетворяет требованиям LiteralType . Это также означает, что все переменные-члены должны быть допустимыми типами LiteralTypes, что предотвращает использование std::string
или std::map
. Вместо этого можно использовать const char *
и std::array
.
Передайте поток ввода и вывода по значению
У вас есть конструкция, в которой вы передаете тип потока в качестве параметра шаблона, а затем заставляете его выводить из него конкретный поток, очень странно, негибко и требует большего набора текста, чем необходимо. Просто добавьте поток ввода и вывода в качестве параметров конструктору:
template <class str, class intype, class outtype>
class ConsoleMenu {
public:
ConsoleMenu(const str& message,
...,
intype &in,
outtype &out);
Сравните:
ConsoleMenu<std::wstring, std::wistream, std::wostream> menu{...}
Против:
ConsoleMenu<std::wstring> menu{..., std::wcin, std::wcout}
Если вы хотите, чтобы стандартный ввод и вывод был параметром по умолчанию, я бы вывести его из строкового типа:
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());
Потому что тогда вы можете просто написать:
ConsoleMenu menu{L"Wide menu", L"invalid", L"> ", L". ", {/* choices */}};