Instead of the loop, we can use std::all_of()
(or std::any_of()
with the opposite condition).
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, too – 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.
We could change the interface to just return the error string, with a null pointer indicating success, to avoid the “out” parameter.
#include <algorithm>
#include <cctype>
#include <string>
// return a null pointer if valid, else a pointer to the error message
const char *check_username_valid(const std::string& str)
{
if (str.length() < MinUsernameLen) { return "Username Too Short"; }
if (str.length() > MaxUsernameLen) { return "Username Too Long"; }
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 "Invalid Character in Username";
}
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 <array>
#include <climits>
bool legal_username_char(unsigned char c)
{
static auto const table
= (){
std::array<bool,UCHAR_MAX+1> a;
for (unsigned char i = 0; i++ < 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.