헤더 전용 일반 콘솔 메뉴 메이커

Dec 20 2020

얼마 전에 명령 줄 메뉴 생성에 대한 질문 에이 답변 을 썼습니다 . 나는 최근 에 그것을 참조 하고 개선하고 싶은 몇 가지를 발견했습니다.

목적

원래 버전과 마찬가지로 목적은 명령 줄 (콘솔) 메뉴의 구성 및 사용을 단순화하는 클래스를 갖는 것입니다.

내가 만든 개선 사항은 다음과 같습니다.

  1. std::string또는 std::wstring프롬프트 및 답변 허용
  2. 사용자가 설명에서 선택자를 분리하도록 허용
  3. 모든 것을 헤더 전용 모듈로 이동
  4. const메뉴 생성 허용
  5. 선택 자로 정렬

질문

내가 궁금한 점은 다음과 같습니다.

  1. 템플릿 매개 변수 이름-개선 할 수 있습니까?
  2. 사용 default_indefault_out-문자열 유형에서 기본값을 유추하는 것이 더 낫습니까?
  3. 의 선택 std::function<void()>각 선택에 대한 작업 등
  4. 의 사용 std::pair대 사용자 정의 개체
  5. 이 모든 것을 네임 스페이스에 래핑해야합니까?
  6. 누락 된 기능이 있습니까?
  7. 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 &in;
    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();
    }
}

답변

3 G.Sliepen Dec 20 2020 at 19:16

질문에 대한 답변

템플릿 매개 변수 이름-개선 할 수 있습니까?

대부분 일관성이 없다는 것입니다. 유형 이름은 대문자로 시작하고 모두 접미사를 붙이 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_indefault_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 */}};