#ifndef MENUGEN_0219820379823487123785 #define MENUGEN_0219820379823487123785 // This is collection of utility constexpr tools for generating menu dynamically in compile time // Menu::generateMenuMessage basically creates this: // constexpr const char* MENU_MSG = "\n\n=== Ticket Booking System ===\n1. Configure\n2. Reserve\n3. Cancel\n4. Check\n5. Exit\n"; // enum class State : int { // MENU = 0, // SETUP = 1, // RESERVE = 2, // CANCEL = 3, // CHECK = 4, // EXIT = 5, // }; // Its kinda long & ugly because we need to generate both symbols in enum and use their values in the message // Menu entries are defined with generic macro X() which is defined where menu is expanded and formats each entry there. // This is trivial for pure enum creation, but we also need to count them, this is achived with Menu::State::COUNT beeing last element // Next step is creating message. To be constexpr we cant use string, I experimented with string_view for few hours, // but they are a lot less versatile than std::array, so Ive got first prototype working with them. // But STL containers are forbidden in CT2... so i wrote my own ConstString :) // With constexpr ready container, generating message is simple as // 1. transforming number(enum member) to string // 2. summing up all the submessage lengths // 3. copying to single container // I was suprised cpp20 doesnt include way to transform int to string in constexpr. // So i achived this with recursive template ExpandDecimal - name is self explanatory. // Calculating total length is just sum of the sizes of expanded template pack // And copying with std::array would be just std::copy_n, but same can be achived on my structure with index_sequence from utility library // thats all, hope you find it interesting :) #include // index_sequence namespace Menu{ constexpr auto RESET = "\033[0m"; constexpr auto RED = "❌ \033[31m"; constexpr auto GREEN = "✅ \033[32m"; constexpr auto YELLOW = "❌ \033[33m"; constexpr auto BOLDWHITE = "\033[1m\033[37m"; constexpr auto CURSOR = "> "; using StateType = size_t; //################################################################################### // helper char[] wrapper since it cannot be directly returned template struct ConstString { char data[size + 1];}; template std::ostream& operator<<(std::ostream& os, const ConstString& r) { for (char c : r.data) os << c; return os; } //################################################################################### // helpers to convert int (from enum) to string in ################################## // shared array to store result template struct to_chars { static const char value[]; }; // convert single digit to char template // constexpr char to_chars::value[] = {('0' + digits)...}; constexpr char to_chars::value[] = {('0' + digits)..., 0}; // null termination // when reminder is not zero - recursive expand for each decimal digit template struct ExpandDecimal : ExpandDecimal {}; // when reminder is not zero - call to_char for last digit template struct ExpandDecimal<0, digits...> : to_chars {}; // entry point - to by called directly template struct NumToString : ExpandDecimal {}; //################################################################################### // helper templates to calculate total length as constexpr ########################## template constexpr std::size_t total_length(Strings&&... strings) { return (0 + ... + std::size(strings)); } //################################################################################### // helper templates to concatonate as constexpr ##################################### template // analog to copy_n from static constexpr void copy_str(char* dest, std::size_t& idx, Str str, std::index_sequence) { ((dest[idx++] = str[I]), ...); } template constexpr auto concat(Strings&&... strings) { constexpr std::size_t total_len = (0 + ... + (std::size(strings) - 1)); ConstString result; std::size_t idx = 0; ((copy_str(result.data, idx, strings, // copy each string to result std::make_index_sequence{}), ...)); result.data[total_len] = '\0'; return result; } //################################################################################### // tests ############################################################################ static_assert(sizeof(NumToString<1>::value) == 2, "NumToString: size check"); static_assert(NumToString<1>::value[0] == '1', "NumToString: 1"); static_assert(NumToString<12345678900>::value[0] == '1' && NumToString<12345678900>::value[1] == '2' && NumToString<12345678900>::value[2] == '3' && NumToString<12345678900>::value[3] == '4' && NumToString<12345678900>::value[4] == '5' && NumToString<12345678900>::value[5] == '6' && NumToString<12345678900>::value[6] == '7' && NumToString<12345678900>::value[7] == '8' && NumToString<12345678900>::value[8] == '9' && NumToString<12345678900>::value[9] == '0' && NumToString<12345678900>::value[10] == '0' && NumToString<12345678900>::value[11] == '\0'&& sizeof(NumToString<12345678900>::value) == 12, "NumToString: longnum"); static_assert(sizeof(concat("a", "bc", "def")) == sizeof("abcdef") , "concat: size check"); static_assert(concat("a", "bc", "def").data[0] == 'a' && concat("a", "bc", "def").data[1] == 'b' && concat("a", "bc", "def").data[2] == 'c' && concat("a", "bc", "def").data[3] == 'd' && concat("a", "bc", "def").data[4] == 'e' && concat("a", "bc", "def").data[5] == 'f' && concat("a", "bc", "def").data[6] == '\0', "concat: content"); } #endif //MENUGEN_0219820379823487123785