There is a list of UnicodeString constants and the corresponding list of integer constants:

const UnicodeString ERR = L"Error"; const UnicodeString READY = L"Ready"; ... const int S_ERR = 0; const int S_READY = 1; ... 

I do not know which container to use and store with this data. If you use them separately, it turns out to be extremely inconvenient to work with them when you need to get a string depending on the state and similar actions:

 switch (Status) { case S_ERR: return ERR; ... } 

What container can I use for this? The answer is very important, I often use this kind of logic.

    4 answers 4

    Once you have an associative relation, the associative container itself suggests itself, namely: std::unordered_map :

     ... const int S_ERR = 0; const int S_READY = 1; ... std::unordered_map<int, UnicodeString> strings; strings.emplace(S_ERR, L"Error"); strings.emplace(S_READY, L"Ready"); ... return strings[Status]; 

    This allows us not to think about the order in which it is stored and not to use the switch , which only inflates the code.

      If the constants are strictly in a row - std :: vector (std :: array). If with holes - std :: map

      Example with a vector ( ideone )

       #include <iostream> #include <array> using namespace std; static constexpr array<wchar_t*,2> strs ={L"azaza",L"ololo"}; int main() { for(const auto i:strs) { wcout << i << endl; } return 0; } 

      And for localization there is PoEdit and its associated framework.

      • And how can you declare a vector of constants? - T2skler
      • @ T2skler - see the answer - gbg
      • And why static at strs ? - ixSci
      • @ixSci, here the static declaration of a variable limits its scope within a given translation unit (generally, within a given file) - aleks.andr
      • @ aleks.andr, this is redundant for const and constexpr they have internal linkage by default - ixSci

      The use of containers in this simple case is redundant.

      To solve the problem described by you, I define an enumeration with numeric constants and write a function that performs a comparison of numbers and strings. Now you need to follow this mapping. In order not to forget about it, I add assert to the mapping function, plus output any noticeable string. Assert triggered in the debug build, and a "noticeable line" in the exhaust build. With this approach, during my practice, unmatched constants did not appear in the final assembly, 100% of the forgotten comparisons were caught at the debugging stage using assert .

      Additionally, usually, if values ​​come from somewhere outside the file or from the network, I write a function to check the values ​​for constants.

      Here is some code like this:

       enum StatusCodes { STATUS_SUCCESS , STATUS_WARNING , STATUS_ERROR , STATUS_CRASH , STATUS_UNKNOWN }; const wchar_t * StatusCodeLabel(int code) { switch(code) { case STATUS_SUCCESS: return L"OK" ; case STATUS_WARNING: return L"Warning"; case STATUS_ERROR : return L"Error" ; case STATUS_CRASH : return L"Crash" ; } assert(false); return L"STATUS CODE WRONG!"; } StatusCodes ValidateStatusCode(int code) { switch(code) { case STATUS_SUCCESS: case STATUS_WARNING: case STATUS_ERROR : case STATUS_CRASH : return code; } assert(false); return STATUS_UNKNOWN; } 

      Such an approach is the cheapest to maintain and is effective in code, even when there are a lot of constants.

      For some reason, support is usually forgotten. Put yourself in the place of a programmer who first reads the code with a "container" mapping. He sees a container, constants, some lines and first thoughts he will have is that it is part of some applied logic, and not just a simple string mapping to constants. This is how little unnecessary complexity accumulates.

        It looks for what you need. Sometimes it is necessary to generate enum 's or functions from such constants. Then boost.preprocessor will help. An example from a recent project:

         #define SB_PROP_TYPES \ /* type name, type, is pointer, is native, array length */ \ /* note: all non-pointer types should be listed */ \ /* in `sb_config_entry`. */ \ /* here, `$` is a valid pointer to `sb_config_entry`, */ \ /* `$$` is a `v_ptr` casted into a proper type. */ \ ((SB_STRING, char, 1, 1, (strlen($$) + 1) )) \ ((SB_LITERAL, char, 1, 1, (strlen($$) + 1) )) \ ((SB_INT, int, 0, 1, )) \ ((SB_BOOL, bool, 0, 1, )) \ ((SB_CHAR_PAIR_ARR, sb_prop_char_pair_array, 1, 0, (1) )) \ ((__SB_UNKNOWN, int, 0, 1, )) // ... enum sb_prop_types { #define __SB_OP(r, data, elem) (BOOST_PP_TUPLE_ELEM(5, 0, elem)) BOOST_PP_SEQ_ENUM(BOOST_PP_SEQ_FOR_EACH(__SB_OP, , SB_PROP_TYPES)) #undef __SB_OP }; 

        Such a thing will unfold in

         enum sb_prop_types { SB_STRING, SB_LITERAL, SB_INT, SB_BOOL, SB_CHAR_PAIR_ARR, __SB_UNKNOWN }; 

        Further, there is such an example - it plays the role of your switch (working with strings, so many ifas are used, but generally you can switch ):

         static inline enum sb_prop_types prop_to_type(const char *__name) { #define __SB_OP(r, data, elem) \ if (strcasecmp(BOOST_PP_TUPLE_ELEM(3, 0, elem), __name) == 0) \ return BOOST_PP_TUPLE_ELEM(3, 2, elem); BOOST_PP_SEQ_FOR_EACH(__SB_OP, _, SB_PROPS) #undef __SB_OP return __SB_UNKNOWN; } 

        Accordingly, it will unfold in a large number of Iphah.