Convierta de forma segura std::string_view a int (como stoi o atoi)

5 minutos de lectura

Hay una estándar seguro manera de convertir std::string_view a int?


Desde C++11 std::string déjanos usar stoi convertir a int:

  std::string str = "12345";
  int i1 = stoi(str);              // Works, have i1 = 12345
  int i2 = stoi(str.substr(1,2));  // Works, have i2 = 23

  try {
    int i3 = stoi(std::string("abc"));
  } 
  catch(const std::exception& e) {
    std::cout << e.what() << std::endl;  // Correctly throws 'invalid stoi argument'
  }

Pero stoi no soporta std::string_view. Entonces, alternativamente, podríamos usar atoipero hay que tener mucho cuidado, por ejemplo:

  std::string_view sv = "12345";
  int i1 = atoi(sv.data());              // Works, have i1 = 12345
  int i2 = atoi(sv.substr(1,2).data());  // Works, but wrong, have i2 = 2345, not 23

Entonces atoi tampoco funciona, ya que se basa en el terminador nulo '\0' (y por ejemplo sv.substr no puede simplemente insertar/agregar uno).

Ahora, desde C++17 también hay from_charspero no parece fallar cuando se proporcionan entradas deficientes:

  try {
    int i3;
    std::string_view sv = "abc";
    std::from_chars(sv.data(), sv.data() + sv.size(), i3);
  }
  catch (const std::exception& e) {
    std::cout << e.what() << std::endl;  // Does not get called
  }

  • Eso es porque std::from_chars no tira nada. En su lugar, devuelve un código de error.

    – Yksisarvinen

    17 de junio de 2019 a las 15:34

  • Al usar std::from_chars, como mencionó @Yksisarvinen, debe verificar el código de error devuelto (result.rc). Además, si tiene la intención de asegurarse de que toda la cadena se convirtió en un número, también debe verificar que result.ptr == sv.data() + sv.size(). De lo contrario, no informará ningún error para cadenas como “12qq”.

    – Algún chico

    9 dic 2022 a las 13:59


Avatar de usuario de Ron
Ron

El std::from_chars la función no arroja, solo devuelve un valor de tipo from_chars_result que es una estructura con dos campos:

struct from_chars_result {
    const char* ptr;
    std::errc ec;
};

Debe inspeccionar los valores de ptr y ec cuando la función devuelve:

#include <iostream>
#include <string>
#include <charconv>

int main()
{
    int i3;
    std::string_view sv = "abc";
    auto result = std::from_chars(sv.data(), sv.data() + sv.size(), i3);
    if (result.ec == std::errc::invalid_argument) {
        std::cout << "Could not convert.";
    }
}

  • Si tiene la intención de asegurarse de que toda la cadena se convirtió en un número, también debe verificar que result.ptr == sv.data() + sv.size() (de lo contrario, no informará ningún error para cadenas como “12qq “), y también debe verificar result.ec==errc{}, no contra std::errc::invalid_argument específicamente, para detectar errores fuera de rango y demás.

    – Algún chico

    9 de diciembre de 2022 a las 13:54

Avatar de usuario de Holt
Bosquecillo

Desafortunadamente, no hay una forma estándar que genere una excepción para usted, pero std::from_chars tiene un código de valor de retorno que puede usar:

#include <charconv>
#include <stdexcept>

template <class T, class... Args>
void from_chars_throws(const char* first, const char* last, T &t, Args... args) {
    std::from_chars_result res = std::from_chars(first, last, t, args... );

    // These two exceptions reflect the behavior of std::stoi.
    if (res.ec == std::errc::invalid_argument) {
        throw std::invalid_argument{"invalid_argument"};
    }
    else if (res.ec == std::errc::result_out_of_range) {
        throw std::out_of_range{"out_of_range"};
    }
}

Obviamente puedes crear svtoi, svtol de esto, pero la ventaja de “extender” from_chars es que solo necesita una sola función con plantilla.

  • Si tiene la intención de asegurarse de que toda la cadena se convirtió en un número, también debe verificar que result.ptr == last. De lo contrario, no informará ningún error para cadenas como “12qq”.

    – Algún chico

    9 dic 2022 a las 13:56

  • @SomeGuy El propósito de esta respuesta es imitar std::stoi comportamiento de lanzamiento. std::stoi no arroja si quedan caracteres restantes, por lo que esta versión no lo hace.

    – Holt

    10 de diciembre de 2022 a las 9:31

  • Aprecio lo que dices, pero sigo pensando que esto es algo que debe ser entendido claramente por cualquiera que lo use. En mi experiencia, casi siempre es deseable convertir toda la cadena pasada, no solo los primeros caracteres de la misma. Desafortunadamente, me he encontrado con bastantes pocos desarrolladores de C++ que entendieron que std::stoi ignora silenciosamente los caracteres finales no analizables, lo que a menudo conduce a errores sutiles. (También me molestó recientemente el hecho de que std::stoi omite los espacios en blanco iniciales, pero std::from_chars no, por lo que su código aún no es un reemplazo directo).

    – Algún chico

    10 dic 2022 a las 13:01


Sobre la base de las excelentes respuestas de @Ron y @Holt, aquí hay un pequeño envoltorio std::from_chars() que devuelve un opcional (std::nullopt cuando la entrada no se puede analizar).

#include <charconv>
#include <optional>
#include <string_view>

std::optional<int> to_int(const std::string_view & input)
{
    int out;
    const std::from_chars_result result = std::from_chars(input.data(), input.data() + input.size(), out);
    if(result.ec == std::errc::invalid_argument || result.ec == std::errc::result_out_of_range)
    {
        return std::nullopt;
    }
    return out;
}

  • ¿Qué pasa cuando ec ¿Hay algún otro código de falla además de esos dos?

    –MM

    1 de marzo de 2021 a las 4:10

  • @MM, lo mejor que puedo decir de los documentos de CppReferenceesos dos son los solo códigos de error que std::from_chars() regresará

    – s3cur3

    1 de marzo de 2021 a las 14:54

  • ¿Por qué no simplemente regresar? out si ec partidos std::errc{} y std::nullopt ¿de lo contrario? ¿No es esa la alternativa menos detallada? yo tambien pienso eso std::string_viewLos s se alinean con los iteradores y pasarlos por valor es el camino a seguir.

    – 303

    6 de enero de 2022 a las 22:43


  • Si tiene la intención de asegurarse de que toda la cadena se convirtió en un número, también debe verificar que result.ptr == input.data()+input.size(). De lo contrario, no informará ningún error para cadenas como “12qq”.

    – Algún chico

    9 dic 2022 a las 13:57

¿Ha sido útil esta solución?