Simple username validation
Instead of the loop, we can use std::all_of() (or std::any_of() with the opposite condition).  That can make the code clearer, because it signals intent at the beginning, and we don't need to unpick control flow as we do with for/return.  With C++20, we are able to pass a range object such as std::string as argument instead of an iterator pair (as we do in C++17).
And we have std::isalnum(), which is likely implemented as a lookup table like this (with the caveat that we need to be careful about signed char).  It's more portable (the code we have will be wrong if there are non-alphabetic characters between A and Z, as there are on EBCDIC systems, for example) and easier to get right (witness the typo - Z instead of z - that means on ASCII-like systems we allow no lower-case letters).
We could change the interface to just return the error string, with a null pointer indicating success, to avoid the &quot;out&quot; parameter.
#include &lt;algorithm&gt;
#include &lt;cctype&gt;
#include &lt;string&gt;
    
// return a null pointer if valid, else a pointer to the error message
const char *check_username_valid(const std::string&amp; str)
{
    if (str.length() &lt; MinUsernameLen) { return &quot;Username Too Short&quot;; }
    if (str.length() &gt; MaxUsernameLen) { return &quot;Username Too Long&quot;; }
    auto const char_permitted
        = [](unsigned char c){ return c == '_' || std::isalnum(c); };

    if (std::all_of(str.begin(), str.end(), char_permitted)) {
        return nullptr;
    }

    return &quot;Invalid Character in Username&quot;;
}

If we'll be adding more permitted username characters, we might want to use the lookup-table approach - but we don't need a new type, and this static const can be built at compilation time:
#include &lt;array&gt;
#include &lt;climits&gt;

bool legal_username_char(unsigned char c)
{
    static auto const table
        = []{
              std::array&lt;bool,UCHAR_MAX+1&gt; a;
              for (unsigned char i = 0;  i++ &lt; UCHAR_MAX; ) {
                  a[i] = std::isalnum(i);
              }
              // additional non-alnum chars allowed
              a['_'] = true;
              return a;
          }();
    return table[c];
}

That idiom is an immediately-invoked lambda, and it's very handy for creating complex constants like this.  Note also the use of UCHAR_MAX rather than making assumptions about the size of the type.
Minor: no need for cast to int when indexing (a[int(((unsigned char)(x)))]).  An unsigned char is perfectly fine as array index.