헤더 전용 일반 콘솔 메뉴 메이커
얼마 전에 명령 줄 메뉴 생성에 대한 질문 에이 답변 을 썼습니다 . 나는 최근 에 그것을 참조 하고 개선하고 싶은 몇 가지를 발견했습니다.
목적
원래 버전과 마찬가지로 목적은 명령 줄 (콘솔) 메뉴의 구성 및 사용을 단순화하는 클래스를 갖는 것입니다.
내가 만든 개선 사항은 다음과 같습니다.
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 */}};