Chaîne conviviale de modèle en numérique en C ++

48

Dans la bibliothèque standard C ++, il existe des fonctions pour convertir des chaînes en types numériques:

stoi
stol
stoll
stoul
stoull
stof
stod
stold

mais je trouve fastidieux de les utiliser dans le code du modèle. Pourquoi il n'y a pas de fonctions de modèle comme:

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

convertir des chaînes en types numériques?

Je ne vois aucune raison technique de ne pas les avoir, mais peut-être que je manque quelque chose. Ils peuvent être spécialisés pour appeler les fonctions nommées sous-jacentes et utiliser enable_if/ conceptspour désactiver les types non numériques.

Existe-t-il des alternatives adaptées aux modèles dans la bibliothèque standard pour convertir la chaîne en types numériques et inversement de manière efficace?

Mircea Ispas
la source
Est-ce que cela répond à votre question? Pourquoi la série `std :: sto` ... n'est-elle pas un modèle?
Boiethios
1
@Boiethios pas vraiment - les réponses à cette question expliquent la raison d'être du "pourquoi", mais elles ne proposent pas de solutions pratiques comme la réponse acceptée. J'ai édité ma question pour demander une alternative pour mieux dire ce dont j'ai besoin
Mircea Ispas

Réponses:

40

Pourquoi il n'y a pas de fonctions de modèle comme:

C ++ 17 a une telle fonction générique chaîne à nombre, mais nommée différemment. Ils sont allés avec std::from_chars, qui est surchargé pour tous les types numériques.

Comme vous pouvez le voir, la première surcharge prend tout type entier comme paramètre de sortie et lui attribuera la valeur si possible.

Il peut être utilisé comme ceci:

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
    }
}

Comme vous pouvez le voir, cela peut fonctionner dans un contexte générique.

Guillaume Racicot
la source
5
C ++ a déstructuré maintenant? : o Déclaration contraignante structurée
Alexander - Rétablir Monica
1
Bien sûr! Cela fonctionne même avec des structures simples ou si la bonne interface est donnée, des classes aussi.
Guillaume Racicot
13

Ce n'est pas un modèle, et cela ne fonctionne pas avec les locales mais si ce n'est pas un stop show alors C ++ 17 a déjà ce que vous voulez: std::from_chars

Il y a des surcharges pour tous les types entiers et à virgule flottante et l'interface est la même, sauf pour les derniers paramètres qui sont respectivement différents pour les types entier et à virgule flottante (mais si la valeur par défaut est correcte, vous n'avez pas besoin de changer quoi que ce soit). Parce que ce n'est pas une fonction locale, elle est également assez rapide. Il va battre n'importe quelle autre chaîne pour convertir la fonction de valeur et généralement c'est par ordre de grandeur.

Il y a une très bonne vidéo CPPCON sur <charconv>(l'en-tête from_charshabite) de Stephan T. Lavavej que vous pouvez regarder sur son utilisation et ses performances ici: https://www.youtube.com/watch?v=4P_kbF0EbZM

NathanOliver
la source
1
@NathanOliver: stoiet ses amis (les conversions mentionnées dans la question) ne fonctionnent pas non plus avec les locales, donc ce n'est pas un showstopper.
Pete Becker
9

Vous ne gagneriez pas grand-chose car dans une expression comme

int x = sto("1");

Il n'existe aucun moyen (facile) de déduire le type souhaité pour le paramètre de modèle. Il faudrait écrire

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

qui dans une certaine mesure va à l'encontre de l'objectif de fournir une fonction générique. D'un autre côté, un

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

serait d'une grande utilité comme vous vous en êtes rendu compte. En C ++ 17 il y en a std::from_chars, qui fait plus ou moins exactement cela (ce n'est pas un modèle mais un ensemble de surcharges et il prend des pointeurs vers des caractères au lieu d'une chaîne, mais ce ne sont que des détails mineurs).

PS Il n'y a pas de moyen facile de déduire le type souhaité dans l'expression ci-dessus, mais il y en a un. Je ne pense pas que le cœur de votre question était exactement la signature que vous avez demandée, et je ne pense pas que ce qui suit est un bon moyen de le mettre en œuvre, mais je savais qu'il existe un moyen de faire la int x = sto("1");compilation ci-dessus et j'étais curieux de le voir en action.

#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;
}

Cela fonctionne comme prévu, mais il présente de graves inconvénients, ce qui est peut-être le plus important, il permet d'écrire auto x = sto(s);, c'est-à-dire qu'il est facile à mal utiliser.

idclev 463035818
la source
Je pense que compter sur la conversion implicite ici est une bonne idée. Essayer de désactiver l'automobile est cependant un problème. En règle générale, j'ai vu cela en mettant une référence const privée dans une classe qui n'est initialisée que par des méthodes valides. Je ne vois pas comment on pourrait exploiter cela ici, car nous devons en quelque sorte construire un objet convertisseur entier avant de continuer. Hmmm ....
bremen_matt
Je peux voir la valeur malgré le paramètre de type non déduit - comme le dit la question, la motivation est de pouvoir utiliser à partir du code du modèle, où vous convertissez en un type qui varie entre les instanciations.
Toby Speight
Quel est le problème fondamental auto x = sto(s)? Cette implémentation particulière se casse car converter::xest une référence qui sort du cadre, mais qui peut être réparée. Supprimez simplement la référence et utilisez std::stringla sémantique de mouvement de.
MSalters
@MSalters oui, c'était la référence que je pensais problématique, mais vous avez raison, pas besoin d'utiliser une référence. Ce qui me dérange le plus, c'est qu'il semble que ce soit une fonction, mais que la fonctionnalité réelle est présente converter, je ne sais pas non plus si l'utilisation d'un opérateur de conversion de modèle était le meilleur choix, des choses qui pourraient être corrigées. Peut-être que ce n'est pas si mal que je le pensais initialement
idclev 463035818
Je ne pense pas qu'il y ait de problème avec la référence const ici. Ma compréhension est que la référence const préservera la durée de vie de la chaîne jusqu'à ce que le convertisseur soit détruit ( herbsutter.com/2008/01/01/… )
bremen_matt
5

La solution compatible avec tous (même les compilateurs C ++ plus anciens comme C ++ - 98) consiste à utiliser boost :: lexical_cast qui est un modèle pour convertir entre les types numérique et chaîne dans les deux sens.

Exemple:

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

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

Łukasz Ślusarczyk
la source
3

Sur les anciennes versions C ++, stringstream est votre ami. Si je comprends bien, les éléments suivants pourraient vous intéresser. C'est 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;
}

Cette méthode fonctionne en C ++ 11 et est assez générale. D'après mon expérience, cette méthode est robuste, mais pas la plus performante.

bremen_matt
la source
Oui, c'est ce que j'ai utilisé, mais les performances sont inférieures aux fonctions nommées, ce qui n'est parfois pas souhaité
Mircea Ispas