Cadena compatible con plantillas a numérico en C++

8 minutos de lectura

avatar de usuario
mircea ispas

En la biblioteca estándar de C++ hay funciones para convertir de cadena a tipos numéricos:

stoi
stol
stoll
stoul
stoull
stof
stod
stold

pero me resulta tedioso usarlos en código de plantilla. Por qué no hay funciones de plantilla algo como:

template<typename T>
T sto(...)

convertir cadenas a tipos numéricos?

No veo ninguna razón técnica para no tenerlos, pero tal vez me estoy perdiendo algo. Se pueden especializar para llamar a las funciones nombradas subyacentes y usar enable_if/concepts para deshabilitar los tipos no numéricos.

¿Existen alternativas compatibles con plantillas en la biblioteca estándar para convertir cadenas en tipos numéricos y viceversa de manera eficiente?

  • ¿Responde esto a tu pregunta? ¿Por qué la serie `std::sto`… no es una plantilla?

    – Boiethios

    20 de febrero de 2020 a las 16:31

  • @Boiethios no realmente: las respuestas de esa pregunta explican la razón detrás del “por qué”, pero no vienen con soluciones prácticas como la respuesta aceptada. He editado mi pregunta para pedir una alternativa para indicar mejor lo que necesito

    –Mircea Ispas

    24 de febrero de 2020 a las 6:43

avatar de usuario
Guillaume Racicot

Por qué no hay funciones de plantilla algo como:

C ++ 17 tiene una función tan genérica de cadena a número, pero con un nombre diferente. se fueron con std::from_charsque está sobrecargado para todos los tipos numéricos.

Como puede ver, la primera sobrecarga está tomando cualquier tipo de entero como parámetro de salida y le asignará el valor si es posible.

Se puede usar así:

template<typename Numeric>
void stuff(std::string_view s) {
    auto value = Numeric{};

    auto [ptr, error] = std::from_chars(s.data(), s.data() + s.size(), value);

    if (error != std::errc{}) {
        // error with the conversion
    } else {
        // conversion successful, do stuff with value
    }
}

Como puede ver, puede funcionar en un contexto genérico.

  • ¿C++ tiene desestructuración ahora? :u Declaración de vinculación estructurada

    – Alejandro

    20 de febrero de 2020 a las 15:19


  • ¡Por supuesto! Incluso funciona con estructuras simples o, si se le da la interfaz correcta, también con clases.

    – Guillaume Racicot

    20 de febrero de 2020 a las 15:21

  • Vale la pena señalar que esto no funcionará con precisión de punto flotante, como se menciona en Reddit y como acabo de probar por mí mismo.

    –Patricio Bertoni

    2 de diciembre de 2020 a las 11:22

  • @PatrizioBertoni, que yo sepa, se supone que funciona con una biblioteca estándar completa de C++ 17. MSVC implementa la versión de coma flotante de las funciones.

    – Guillaume Racicot

    2 de diciembre de 2020 a las 18:31

avatar de usuario
NathanOliver

No es una plantilla, y no funciona con locales, pero si eso no es un impedimento, entonces C++17 ya tiene lo que desea: std::from_chars

Hay sobrecargas para todos los tipos de números enteros y de punto flotante y la interfaz es la misma excepto por los últimos parámetros que son diferentes para los tipos de números enteros y de punto flotante respectivamente (pero si el valor predeterminado está bien, entonces no es necesario Cambia cualquier cosa). Debido a que esta no es una función consciente de la configuración regional, también es bastante rápida. Superará a cualquiera de las otras funciones de conversión de cadena a valor y, en general, es por órdenes de magnitud.

Hay un muy buen video de CPPCON sobre <charconv> (el encabezado from_chars vive en) por Stephan T. Lavavej que puede ver sobre su uso y rendimiento aquí: https://www.youtube.com/watch?v=4P_kbF0EbZM

  • @NathanOliver: stoi y sus amigos (las conversiones mencionadas en la pregunta) tampoco funcionan con locales, por lo que no es un problema.

    – Pete Becker

    19 de febrero de 2020 a las 14:35

avatar de usuario
463035818_no_es_un_número

No ganarías mucho porque en una expresión como

int x = sto("1");

No existe una forma (fácil) de deducir el tipo deseado para el parámetro de plantilla. tendrías que escribir

int x = sto<int>("1");

que hasta cierto punto anula el propósito de proporcionar una función genérica. Por otro lado, un

template<typename T>
void sto(std::string x,T& t);

Sería de buen uso como te diste cuenta. En C++17 hay std::from_charsque hace más o menos exactamente eso (no es una plantilla sino un conjunto de sobrecargas y lleva punteros a caracteres en lugar de una cadena, pero eso es solo detalles menores).

PD
No hay una manera fácil de deducir el tipo deseado en la expresión anterior, pero hay una manera. No creo que el núcleo de su pregunta fuera exactamente la firma que solicitó, y no creo que la siguiente sea una buena manera de implementarla, pero sabía que hay una manera de hacer lo anterior. int x = sto("1"); compile y tenía curiosidad por verlo en acción.

#include <iostream>
#include <string>

struct converter {
    const std::string& x;
    template <typename T> operator T() { return 0;}
};

template <> converter::operator int() { return stoi(x); }
template <> converter::operator double() { return stod(x); }
converter sto(const std::string& x) { return {x}; }

int main() {
    std::string s{"1.23"};
    int x = sto(s);
    double y = sto(s);
    std::cout << x << " " << y;
}

Esto funciona según lo previsto, pero tiene graves inconvenientes, quizás lo más importante es que permite escribir auto x = sto(s);es decir, es fácil de usar mal.

  • Creo que confiar en la conversión implícita aquí es una buena idea. Sin embargo, tratar de deshabilitar el modo automático es un problema. Por lo general, he visto que se hace al colocar una referencia de const privada en una clase que solo se inicializa mediante métodos válidos. Sin embargo, no puedo ver cómo uno podría aprovechar eso aquí porque tenemos que construir un objeto convertidor completo antes de continuar. Mmm….

    – bremen_matt

    20 de febrero de 2020 a las 8:13

  • Puedo ver el valor a pesar del parámetro de tipo no deducido: como dice la pregunta, la motivación es poder usar desde el código de la plantilla, donde se está convirtiendo a un tipo que varía entre las instancias.

    –Toby Speight

    20 de febrero de 2020 a las 11:59

  • ¿Cuál es el problema fundamental con auto x = sto(s) ? Esta implementación particular se rompe porque converter::x es una referencia que está fuera del alcance, pero que se puede arreglar. Simplemente elimine la referencia y confíe en std::stringLa semántica de los movimientos.

    – MSalters

    20 de febrero de 2020 a las 16:15

  • @MSalters sí, fue la referencia que pensé que era problemática, pero tiene razón, no es necesario usar una referencia. Lo que realmente me molesta más es que parece ser una función, pero la funcionalidad real está en converter, tampoco estoy seguro de si usar un operador de conversión de plantilla fue la mejor opción, cosas que podrían solucionarse. Tal vez no es tan malo como pensé inicialmente

    – 463035818_no_es_un_número

    20 de febrero de 2020 a las 16:20


  • No creo que haya ningún problema con la referencia const aquí. Tengo entendido que la referencia const preservará la vida útil de la cadena hasta que se destruya el convertidor (herbsutter.com/2008/01/01/…)

    – bremen_matt

    20 de febrero de 2020 a las 19:30


La solución compatible con todos (incluso los compiladores de C++ más antiguos como los de C++-98) es usar impulso::lexical_cast que es una plantilla para convertir entre tipos numéricos y de cadena en ambos sentidos.

Ejemplo:

short myInt = boost::lexical_cast<short>(*argv);
std::string backToString = boost::lexical_cast<std::string>(myInt);

Ver: https://www.boost.org/doc/libs/1_42_0/libs/conversion/lexical_cast.htm

avatar de usuario
bremen_matt

En versiones anteriores de C++, stringstream es tu amigo. Si entiendo correctamente, entonces lo siguiente podría ser interesante para usted. Es C++11.

https://wandbox.org/permlink/nUNiUwWWTr7a0NXM

#include <sstream>
#include <string>
#include <iostream>

template<typename T, typename String>
T sto(const String & str) {
    T val;
    std::stringstream ss(str);
    ss >> val;
    return val;
}

template<typename T, typename String>
void sto(const String & str, T & val) {
    std::stringstream ss(str);
    ss >> val;
}

int main() {   
    std::cout << sto<float>("1.1") << ", " << sto<int>(std::string{"2"});

    // An alternative version that infers the type 
    double d;
    sto("3.3", d);
    std::cout << ", " << d;
}

Este método funciona en C++ 11 y es bastante general. En mi experiencia, este método es robusto, pero no el más eficaz.

  • Sí, esto es lo que he usado, pero el rendimiento está por debajo de las funciones nombradas, lo que a veces no se desea.

    –Mircea Ispas

    20 de febrero de 2020 a las 8:43

  • Sí, esto es lo que he usado, pero el rendimiento está por debajo de las funciones nombradas, lo que a veces no se desea.

    –Mircea Ispas

    20 de febrero de 2020 a las 8:43

¿Ha sido útil esta solución?