#Optimized custom constexpr string_view integer converter and validator

3 messages · Page 1 of 1 (latest)

sharp sphinx
#

This works well but im wondering if theres anything I can do better
Made for embedded so needs to be fast and efficient

/**
 * @brief Converts a string view to an integral value of type T.
 *
 * This function attempts to parse a string representation of a number into an integer of the specified integral type T.
 * It performs validation to ensure the string is a valid representation of a number within the range of the target type and checks for overflow or invalid input.
 *
 * @tparam T The integral type to which the string should be converted.
 * @param[in] str The input string view representing the number.
 * @param[out] out The reference where the converted value will be stored, if successful.
 * @return true if the conversion was successful and `out` contains the parsed value.
 * @return false if the conversion failed due to invalid input, overflow, or out-of-range value.
 *
 * @note The function handles both signed and unsigned types, for signed types, it processes negative numbers correctly.
 * @note It also validates the input for edge cases like empty strings, leading zeros, or excessive length.
 *
 * @warning Ensure the input string is sanitized if sourced from untrusted input, as this function does not strip whitespace or handle non-numeric characters beyond validation.
 */
template<typename T>
constexpr bool spanToT(std::string_view str, T& out)
{
  static_assert(std::is_integral_v<T>, "Template type T must be an integral type.");

  // Define the unsigned type equivalent for T.
  using UT = typename std::make_unsigned<T>::type;

  constexpr bool IsSigned = std::is_signed_v<T>;

  // Define an overflow threshold for type T.
  // If an integer of type T exceeds this value, multiplying it by 10 will cause an overflow.
  constexpr T Threshold = std::numeric_limits<T>::max() / 10;

  // Fail on empty input strings.
  if (str.empty()) {
    return false;
  }

  // Handle negative numbers for signed types.
  bool isNegative = false;
  if constexpr (IsSigned) {
    if (str.front() == '-') {
      str.remove_prefix(1);  // Remove the negative sign.
      isNegative = true;

      // Fail if the string only contained a negative sign with no numeric characters
      if (str.empty()) {
        return false;
      }
    }
  }

  // Fail on strings that are too long to be valid integers.
  if (str.length() > OpenShock::Util::Digits10CountMax<T>) {
    return false;
  }

  // Special case for zero, also handles leading zeros and negative zero
  if (str.front() == '0') {
    // Fail if string has leading or negative zero's
    if (str.length() > 1 || isNegative) return false;

    // Simple "0" string
    out = 0;
    return true;
  }

  // Value accumulator.
  UT val = 0;

  for (char c : str) {
    // Return false if the character is not a digit.
    if (c < '0' || c > '9') {
      return false;
    }

    // Check for multiplication overflow before multiplying by 10.
    if (val > Threshold) {
      return false;
    }

    val *= 10;

    // Convert the character to a digit and check for addition overflow.
    uint8_t digit = c - '0';
    if (digit > std::numeric_limits<UT>::max() - val) {
      return false;
    }

    val += digit;
  }

  // Special case handling for signed integers.
  if constexpr (IsSigned) {
    if (isNegative) {
      // Signed cast undeflow check, checks against the inverse lower limit for the signed type (e.g., 128 for int8_t).
      if (val > static_cast<UT>(std::numeric_limits<T>::max()) + 1) {
        return false;
      }

      // Assign negative value bits
      val = ~val + 1;
    } else if (val > std::numeric_limits<T>::max()) {
      // Fail on signed cast overflow
      return false;
    }
  }

  // Cast result to output and return
  out = static_cast<T>(val);
  return true;
}
wet rapids
#

atoi and stoi are not known for being speedy

#

try comparing against std::from_chars