Ma ktoś pomyśł jak naprawić problem wiszących referencji w metodzie `menu::add_submenu`? Jak w ogóle poprawić można mój kod?
main.cpp
// the purpose of the below code is to test menu class
#include "menu.h"
#include <cstdlib>
#include <iostream>
int main() {
std::cin.exceptions(std::cin.eofbit);
try {
pt::menu main_menu{"Main menu"};
main_menu.add_item("item 1",
[]() { std::cout << "item 1" << std::endl; });
main_menu.add_item("item 2",
[]() { std::cout << "item 2" << std::endl; });
{
pt::menu submenu{"Submenu"};
main_menu.add_submenu(submenu);
}
main_menu.loop();
} catch (const std::exception& e) {
std::cerr << e.what() << '\n';
return EXIT_FAILURE;
}
std::cout << "Good bye!\n";
return 0;
}
menu.h
#ifndef MENU_H_
#define MENU_H_
#include <functional>
#include <iosfwd>
#include <string>
#include <utility>
#include <vector>
namespace pt {
struct menu_item {
menu_item(std::string title_val, std::function<void()> action_val);
friend std::ostream& operator<<(std::ostream& lhs, const menu_item& rhs);
std::string title{};
std::function<void()> action{};
};
class menu {
public:
explicit menu(std::string title_val, std::string description_val = "",
std::vector<menu_item> items_val = {});
void add_item(menu_item item);
void add_item(std::string title_val, std::function<void()> action);
void add_item_at(int index, menu_item item);
void add_item_at(int index, std::string title_val,
std::function<void()> action);
void remove_item(int index);
void add_submenu(menu menu_val);
void add_submenu(std::string title_val, menu menu_val);
void add_submenu_at(int index, menu menu_val);
void add_submenu_at(int index, std::string title_val, menu menu_val);
void set_title(std::string title_val);
void set_description(std::string description_val);
void loop() const;
private:
// helper functions
void print_choices() const;
char read_choice() const;
void check_index(std::size_t index) const;
void print_title(std::string_view title) const;
// wrapper function for menu::loop
std::function<void()> invoke_menu_loop(const menu& menu_val);
static constexpr char quit_choice = 'q';
static int nesting_level;
std::string title{};
std::string description{};
std::vector<menu_item> items{};
};
} // namespace pt
#endif // MENU_H_
menu.cpp
#include "menu.h"
#include <iostream>
#include <sstream>
#include <stdexcept>
#include <string>
#include <utility>
#include <vector>
namespace pt {
// menu_item constructors
menu_item::menu_item(std::string title_val, std::function<void()> action_val)
: title{std::move(title_val)}, action{std::move(action_val)} {}
// menu_item struct i/o operators
std::ostream& operator<<(std::ostream& lhs, const menu_item& rhs) {
return lhs << rhs.title;
}
// menu class constructors
menu::menu(std::string title_val, std::string description_val,
std::vector<menu_item> items_val)
: title{std::move(title_val)}, description{std::move(description_val)},
items{std::move(items_val)} {}
// menu class static variables
int menu::nesting_level{0};
// menu class public interface
void menu::add_item(menu_item item) { items.push_back(std::move(item)); }
void menu::add_item(std::string title_val, std::function<void()> action_val) {
items.emplace_back(std::move(title_val), std::move(action_val));
}
void menu::add_item_at(int index, menu_item item) {
check_index(index);
items.insert(items.begin() + index, std::move(item));
}
void menu::add_item_at(int index, std::string title_val,
std::function<void()> action) {
check_index(index);
items.emplace(items.begin() + index, std::move(title_val),
std::move(action));
}
void menu::remove_item(int index) {
check_index(index);
items.erase(items.begin() + index);
}
void menu::add_submenu(menu menu_val) {
items.emplace_back(menu_val.title, invoke_menu_loop(menu_val));
}
void menu::add_submenu(std::string title_val, menu menu_val) {
items.emplace_back(title_val, invoke_menu_loop(menu_val));
}
void menu::add_submenu_at(int index, menu menu_val) {
check_index(index);
items.insert(items.begin() + index,
menu_item{menu_val.title, invoke_menu_loop(menu_val)});
}
void menu::add_submenu_at(int index, std::string title_val, menu menu_val) {
check_index(index);
items.insert(items.begin() + index,
menu_item{title_val, invoke_menu_loop(menu_val)});
}
void menu::set_title(std::string title_val) { title = std::move(title_val); }
void menu::set_description(std::string description_val) {
description = std::move(description_val);
}
void menu::loop() const {
bool active{true};
++nesting_level;
while (active) {
print_title(title);
if (!description.empty()) {
std::cout << std::endl;
std::cout << description;
std::cout << std::endl;
}
char choice{read_choice()};
if (choice == quit_choice) {
--nesting_level;
active = false;
} else {
std::cout << '\n';
std::size_t index{static_cast<std::size_t>(choice) - '0'};
if (index > items.size()) {
std::cout << "Invalid choice!\n";
continue;
}
// call function bound to menu item
items[choice - '1'].action();
// wait until user press the enter key
std::cout << "\nPress enter to continue..." << std::endl;
std::string line;
std::getline(std::cin, line);
}
}
}
// menu class private functions
void menu::print_choices() const {
for (std::size_t i = 0; i < items.size(); ++i) {
std::cout << i + 1 << ") " << items[i] << '\n';
}
std::cout << "q) " << (nesting_level > 1 ? "Back" : "Quit");
std::cout << std::endl;
}
char menu::read_choice() const {
std::string user_input;
char choice;
bool valid{false};
std::cout << "What do you want to do?\n";
do {
print_choices();
std::cout << "Your choice: ";
std::getline(std::cin, user_input);
if (user_input.length() > 1 || user_input.empty()) {
std::cout << "Please enter the valid choice.\n";
continue;
}
choice = tolower(user_input.at(0));
if ((choice - '1') < 0 ||
(static_cast<std::size_t>(choice - '1') >= items.size() &&
(choice != quit_choice))) {
std::cout << "Invalid choice!\n";
} else {
valid = true;
}
} while (!valid);
return choice;
}
void menu::check_index(std::size_t index) const {
const std::size_t max_allowed_idx{items.size() - 1};
if (index > max_allowed_idx) {
std::ostringstream oss;
oss << "index value must be between " << index << " and "
<< max_allowed_idx;
throw std::out_of_range{oss.str()};
}
}
void menu::print_title(std::string_view title) const {
std::size_t title_len = title.size();
std::size_t width = title_len + 4;
for (std::size_t i = 0; i < width; ++i) {
std::cout << "*";
}
std::cout << "\n* " << title << " *\n";
for (std::size_t i = 0; i < width; ++i) {
std::cout << "*";
}
std::cout << std::endl;
}
// wrapper function for menu::loop
std::function<void()> menu::invoke_menu_loop(const menu& the_menu) {
return [&the_menu]() { the_menu.loop(); };
}
} // namespace pt