Cadena amigable de plantilla a numérico en C ++

48

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 el código de plantilla. ¿Por qué no hay funciones de plantilla?

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/ conceptspara deshabilitar tipos no numéricos.

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

Mircea Ispas
fuente
¿Responde esto a tu pregunta? ¿Por qué la serie `std :: sto` ... no es una plantilla?
Boiethios
1
@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
expresar

Respuestas:

40

¿Por qué no hay funciones de plantilla?

C ++ 17 tiene una función genérica de cadena a número, pero se nombra de manera diferente. Fueron con std::from_chars, que está sobrecargado para todos los tipos numéricos.

Como puede ver, la primera sobrecarga toma cualquier tipo 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) {
        // error with the conversion
    } else {
        // conversion successful, do stuff with value
    }
}

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

Racicot Guillaume
fuente
55
C ++ tiene desestructuración ahora? : o Declaración vinculante estructurada
Alexander - Restablece a Monica
1
¡Por supuesto! Incluso funciona con estructuras simples o si se le da la interfaz correcta, las clases también.
Guillaume Racicot
13

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

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

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

NathanOliver
fuente
1
@NathanOliver: stoiy sus amigos (las conversiones mencionadas en la pregunta) tampoco funcionan con las configuraciones regionales, por lo que no es un showtopper.
Pete Becker
9

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

int x = sto("1");

No hay forma (fácil) de deducir el tipo deseado para el parámetro de plantilla. Tendrias que escribir

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

que en cierta medida derrota 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 gran utilidad como te diste cuenta. En C ++ 17 existe std::from_chars, que hace más o menos exactamente eso (no es una plantilla sino un conjunto de sobrecargas y toma punteros a caracteres en lugar de una cadena, pero eso son 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 sea 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 la int x = sto("1");compilación anterior y tenía curiosidad por verla. 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 incorrectamente.

idclev 463035818
fuente
Creo que confiar en la conversión implícita aquí es una buena idea. Sin embargo, intentar desactivar el auto es un problema. Por lo general, lo he visto al poner una referencia de const privada en una clase que solo se inicializa por métodos válidos. Sin embargo, no puedo ver cómo se aprovecharía eso porque tenemos que construir un objeto convertidor completo antes de continuar. Hmmm ....
bremen_matt
Puedo ver el valor a pesar del parámetro de tipo no deducido; como dice la pregunta, la motivación es poder usar desde dentro del código de plantilla, donde estás convirtiendo a un tipo que varía entre instancias.
Toby Speight
¿Cuál es el problema fundamental con auto x = sto(s)? Esta implementación particular se rompe porque converter::xes una referencia que está fuera de alcance, pero eso se puede solucionar. Simplemente elimine la referencia y confíe en std::stringla semántica de movimiento.
MSalters
@MSalters sí, fue la referencia lo que pensé que es problemático, pero tienes 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, además, no estoy seguro de si usar un operador de conversión de plantillas fue la mejor opción, cosas que podrían solucionarse. Tal vez no sea tan malo como pensé inicialmente
idclev 463035818
No creo que haya ningún problema con la referencia constante aquí. Tengo entendido que la referencia constante preservará la vida útil de la cadena hasta que se destruya el convertidor ( hierbasutter.com/2008/01/01/… )
bremen_matt
5

La solución compatible con todos (incluso los compiladores de C ++ más antiguos, como los de C ++ - 98) es utilizar boost :: lexical_cast, que es una plantilla para convertir entre tipos numéricos y de cadena de ambas maneras.

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

Łukasz Ślusarczyk
fuente
3

En versiones anteriores de C ++, stringstream es tu amigo. Si lo 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.

bremen_matt
fuente
Sí, esto es lo que he usado, pero el rendimiento está debajo de las funciones nombradas que a veces no se desea
Mircea Ispas