Improving the Qt/C++ QString::toLongLong() and QString::toULongLong() function signatures
Qt/C++ provides two string-to-int conversion functions, QString::toLongLong() and QString::toULongLong(). These functions require a pointer to a boolean variable to receive the success or failure of the conversion:
bool ok;
qint64 value = str.toLongLong(&ok);
if (ok) {
// do something
} else {
// handle error
}
Notice that to use these functions properly, we must create not one, but two variables.
Since the success of the conversion must be checked in any case (and preferably only once), a cleaner approach would be to alter the function signatures to require a reference to the variable that will receive the conversion result, and return the success or failure as the main function result:
qint64 value;
if (str_to_int(str, value)) {
// do something
} else {
// handle error
}
This would save us from having to declare a boolean variable that only gets checked once.
We can use templates to implement the desired behavior. Side benefits include being able to handle both signed and unsigned conversions with a single function, and we can return false if the converted value would overflow or underflow the referenced variable.
/*
* Examine the first two characters of a numeric string to determine what base it's in.
*/
static qint8 infer_base(const QString str) {
if (str.length() >= 2 && str[0] == '0') {
switch (str[1].toLatin1()) {
case 'b':
case 'B':
return 2;
case 'o':
case 'O':
return 8;
case 'x':
case 'X':
return 16;
}
}
return 10;
}
/*
* Demote an integer to a smaller datatype.
* Return true if successful.
* Return false if there is an overflow or underflow.
*/
template<typename T1, typename T2, typename std::enable_if<std::is_integral<T1>::value && std::is_integral<T2>::value>::type* = nullptr>
static bool demote [[nodiscard]] (T1 source, T2 &dest) {
dest = static_cast<T2>(source);
return static_cast<T1>(dest) == source;
}
/*
* Convert a numeric string to an integer.
* Return true if the conversion is successful and the integer fits in the referenced variable.
* Return false otherwise.
*/
template<typename T, typename std::enable_if<std::is_integral<T>::value>::type* = nullptr>
static bool str_to_int [[nodiscard]] (QString str, T &value, qint8 base = -1) {
if (base == -1) {
base = infer_base(str);
if (base != 10) {
str = str.mid(2);
}
}
if (base < 2) {
return false;
}
bool ok;
if (std::numeric_limits<T>::is_signed) {
qint64 i = str.toLongLong(&ok, base);
return ok && demote(i, value);
} else {
if (str.length() >= 1 && str[0] == '-') {
return false;
}
quint64 u = str.toULongLong(&ok, base);
return ok && demote(u, value);
}
}