I'm implementing a function called is_proper_integer
that will check the string whether it is a valid integer literal by borrowing the idea from C++ grammar.
Function signature:
constexpr bool is_proper_integer(std::string_view str,
std::optional<std::reference_wrapper<parse_integer_errc_t>> errc = {});
Where:
- the function is declared as
constexpr
so that the function is usable at compile-time. str
has the plain typestd::string_view
so that I could modify it by callingremove_prefix
andremove_suffix
within the definition.errc
acts as an error code to report the reason why it returnsfalse
and I made itstd::optional<std::reference_wrapper<...>>
because the parameter is obviously optional and there is nostd::optional<T&>
, so I wrap it inside the class templatestd::reference_wrapper
.
These are the helpful utilities in order to work with the function:
enum class parse_integer_errc_t {
normal,
foreign_char,
adjacent_radix_sep,
invalid_prefix,
invalid_radix_sep,
invalid_binary_fmt,
invalid_octal_fmt,
invalid_decimal_fmt,
invalid_hexadecimal_fmt,
unknown
};
enum class integer_prefix_t {
binary, octal, decimal, hexadecimal, unknown
};
std::ostream& operator<<(std::ostream& os, parse_integer_errc_t errc) {
switch (errc) {
using enum parse_integer_errc_t;
case normal: return os << "normal";
case foreign_char: return os << "foreign_char";
case adjacent_radix_sep: return os << "adjacent_radix_sep";
case invalid_prefix: return os << "invalid_prefix";
case invalid_radix_sep: return os << "invalid_radix_sep";
case invalid_binary_fmt: return os << "invalid_binary_fmt";
case invalid_octal_fmt: return os << "invalid_octal_fmt";
case invalid_decimal_fmt: return os << "invalid_decimal_fmt";
case invalid_hexadecimal_fmt: return os << "invalid_hexadecimal_fmt";
case unknown: return os << "unknown";
}
return os;
}
Main implementation:
constexpr bool is_proper_integer(std::string_view str,
std::optional<std::reference_wrapper<parse_integer_errc_t>> errc = {}) {
constexpr char valid_chars[] = {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'A', 'B', 'C', 'D', 'E', 'F',
'a', 'b', 'c', 'd', 'e', 'f'
};
/// span version
auto&& span_chars = std::span{valid_chars};
/// check if it has unary '-' or '+' operator
if (str.front() == '-' || str.front() == '+') str.remove_prefix(1);
/// check if the front character is valid (after '-')
if (
auto&& span_ = span_chars.first<10>();
std::ranges::find(span_, str.front()) == span_.end()
) {
if (errc.has_value()) errc->get() = parse_integer_errc_t::invalid_prefix;
return false;
}
/// check the prefix
integer_prefix_t flag_ = integer_prefix_t::unknown;
if (str.starts_with("0x") | str.starts_with("0X")) {
str.remove_prefix(2);
flag_ = integer_prefix_t::hexadecimal;
} else if (str.starts_with("0b") || str.starts_with("0B")) {
str.remove_prefix(2);
flag_ = integer_prefix_t::binary;
} else if (str.starts_with('0')) {
str.remove_prefix(1);
flag_ = integer_prefix_t::octal;
} else {
flag_ = integer_prefix_t::decimal;
}
/// check again...
if (str.starts_with('\'')) {
if (errc.has_value()) errc->get() = parse_integer_errc_t::invalid_radix_sep;
return false;
}
/// check the suffix
if (str.ends_with("ull") ||
str.ends_with("ULL") ||
str.ends_with("uLL") ||
str.ends_with("Ull")
) str.remove_suffix(3);
else if (
str.ends_with("ul") ||
str.ends_with("uL") ||
str.ends_with("Ul") ||
str.ends_with("UL") ||
str.ends_with("ll") ||
str.ends_with("LL") ||
str.ends_with("uz") ||
str.ends_with("uZ") ||
str.ends_with("Uz") ||
str.ends_with("UZ")
) str.remove_suffix(2);
else if (
str.ends_with('z') ||
str.ends_with('Z') ||
str.ends_with('u') ||
str.ends_with('U') ||
str.ends_with('l') ||
str.ends_with('L')
) str.remove_suffix(1);
/// check again...
if (str.ends_with('\'')) {
if (errc.has_value()) errc->get() = parse_integer_errc_t::invalid_radix_sep;
return false;
}
/// flag for radix grouping check
bool radix_sep_state { false };
/// normal loop checking
for (char c : str) {
if (c == '\'') {
if (radix_sep_state) {
if (errc.has_value()) errc->get() = parse_integer_errc_t::adjacent_radix_sep;
return false;
}
radix_sep_state = true;
continue;
}
radix_sep_state = false;
if (std::ranges::find(span_chars, c) == span_chars.end()) {
if (errc.has_value()) errc->get() = parse_integer_errc_t::foreign_char;
return false;
}
switch (flag_) {
using enum integer_prefix_t;
case binary: {
auto&& span_ = span_chars.first<2>();
if (std::ranges::find(span_, c) == span_.end()) {
if (errc.has_value()) errc->get() = parse_integer_errc_t::invalid_binary_fmt;
return false;
}
break;
}
case octal: {
auto&& span_ = span_chars.first<8>();
if (std::ranges::find(span_, c) == span_.end()) {
if (errc.has_value()) errc->get() = parse_integer_errc_t::invalid_octal_fmt;
return false;
}
break;
}
case decimal: {
auto&& span_ = span_chars.first<10>();
if (std::ranges::find(span_, c) == span_.end()) {
if (errc.has_value()) errc->get() = parse_integer_errc_t::invalid_decimal_fmt;
return false;
}
break;
}
case hexadecimal: {
if (std::ranges::find(span_chars, c) == span_chars.end()) {
if (errc.has_value()) errc->get() = parse_integer_errc_t::invalid_hexadecimal_fmt;
return false;
}
break;
}
case unknown: {
if (errc.has_value()) errc->get() = parse_integer_errc_t::unknown;
return false;
}
}
}
if (errc.has_value()) errc->get() = parse_integer_errc_t::normal;
return true;
}
Compile-time Check:
static_assert(is_proper_integer("1234")); /// normal case
static_assert(is_proper_integer("+1234")); /// positive
static_assert(is_proper_integer("-1234")); /// negative
static_assert(is_proper_integer("1234u")); /// has suffix 'u'
static_assert(is_proper_integer("-1234ull")); /// has suffix 'ull' (ofc, overflow, but still valid)
static_assert(is_proper_integer("1234uz")); /// has suffix 'uz'
static_assert(is_proper_integer("0b10010")); /// ok, binary
static_assert(!is_proper_integer("0b1012")); /// '2' strayed in binary fmt
static_assert(is_proper_integer("+0B11001U")); /// has suffix 'U'
static_assert(!is_proper_integer("ajsdsad")); /// obviously, invalid chars
static_assert(is_proper_integer("0172613")); /// octal, ok
static_assert(is_proper_integer("-012112")); /// ok
static_assert(is_proper_integer("+0xabc'def")); /// hexadecimal, ok
static_assert(is_proper_integer("-0XCAFEdead")); /// ok, case-insensitive
static_assert(is_proper_integer("0xdeadbeefull")); /// ok, has suffix 'ull'
static_assert(!is_proper_integer("deadbeef")); /// invalid prefix
static_assert(!is_proper_integer("12'3''2")); /// wrong radix grouping.
static_assert(is_proper_integer("0b1111'0000'1010ull")); /// ok
static_assert(!is_proper_integer("0123;1")); /// foreign character
Runtime Check:
std::vector<std::string_view> queues {
"1234", "+1234", "-1234", "1234u",
"-1234ull", "1234uz", "0b10010",
"0b1012", "+0B11001U", "ajsdsad",
"0172613", "-012112", "+0xabc'def",
"-0XCAFEdead", "0xdeadbeefull", "deadbeef",
"12'3''2", "0b1111'0000'1010ull", "0123;1"
};
parse_integer_errc_t errcode;
std::cout << std::boolalpha;
for (const auto& str : queues) {
std::cout
<< "String: " << std::setw(23) << std::left << str
<< "Is valid?: " << std::setw(8) << std::left
<< (is_proper_integer(str, errcode) ? "yes" : "no")
<< "Reason: " << std::setw(20) << std::left << errcode << '\n';
}
Output:
String: 1234 Is valid?: yes Reason: normal
String: +1234 Is valid?: yes Reason: normal
String: -1234 Is valid?: yes Reason: normal
String: 1234u Is valid?: yes Reason: normal
String: -1234ull Is valid?: yes Reason: normal
String: 1234uz Is valid?: yes Reason: normal
String: 0b10010 Is valid?: yes Reason: normal
String: 0b1012 Is valid?: no Reason: invalid_binary_fmt
String: +0B11001U Is valid?: yes Reason: normal
String: ajsdsad Is valid?: no Reason: invalid_prefix
String: 0172613 Is valid?: yes Reason: normal
String: -012112 Is valid?: yes Reason: normal
String: +0xabc'def Is valid?: yes Reason: normal
String: -0XCAFEdead Is valid?: yes Reason: normal
String: 0xdeadbeefull Is valid?: yes Reason: normal
String: deadbeef Is valid?: no Reason: invalid_prefix
String: 12'3''2 Is valid?: no Reason: adjacent_radix_sep
String: 0b1111'0000'1010ull Is valid?: yes Reason: normal
String: 0123;1 Is valid?: no Reason: foreign_char
Is there anything I could simplify?