Stringa amichevole del modello numerica in C ++


48

Nella libreria standard C ++ ci sono funzioni per convertire da stringhe a tipi numerici:

stoi
stol
stoll
stoul
stoull
stof
stod
stold

ma trovo noioso usarli nel codice modello. Perché non ci sono funzioni modello simili a:

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

convertire le stringhe in tipi numerici?

Non vedo alcun motivo tecnico per non averli, ma forse mi manca qualcosa. Possono essere specializzati per chiamare le funzioni nominate sottostanti e utilizzare enable_if/ conceptsper disabilitare tipi non numerici.

Esistono alternative compatibili con i modelli nella libreria standard per convertire le stringhe in tipi numerici e viceversa in modo efficiente?



1
@Boiethios non proprio - le risposte a quella domanda spiegano la logica alla base del "perché", ma non arrivano con soluzioni pratiche come la risposta accettata. Ho modificato la mia domanda per chiedere un'alternativa per affermare meglio ciò di cui ho bisogno
Mircea Ispas,

Risposte:


40

Perché non ci sono funzioni modello simili a:

C ++ 17 ha tale stringa generica in funzione numerica, ma denominata diversamente. Sono andati con std::from_chars, che è sovraccarico per tutti i tipi numerici.

Come puoi vedere, il primo sovraccarico sta prendendo qualsiasi tipo intero come parametro di output e gli assegnerà il valore se possibile.

Può essere usato in questo modo:

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

Come puoi vedere, può funzionare in un contesto generico.


5
Il C ++ ha subito una destrutturazione? : o Dichiarazione di associazione strutturata
Alexander - Ripristina Monica il

1
Ovviamente! Funziona anche con semplici strutture o, se viene fornita la giusta interfaccia, anche le classi.
Guillaume Racicot,

13

Non è un modello e non funziona con le versioni locali ma se questo non è un blocco dello spettacolo, C ++ 17 ha già quello che vuoi: std::from_chars

Esistono sovraccarichi per tutti i tipi di numeri interi e in virgola mobile e l'interfaccia è la stessa ad eccezione degli ultimi parametri che sono diversi rispettivamente per i tipi di numeri interi e in virgola mobile (ma se il valore predefinito va bene non è necessario cambiare qualcosa). Poiché questa non è una funzione compatibile con le impostazioni locali, è anche abbastanza veloce. Batterà qualsiasi altra stringa per valutare la funzione di conversione e generalmente lo è per ordini di grandezza.

C'è un ottimo video CPPCON su <charconv>(l'intestazione from_charsvive) di Stephan T. Lavavej che puoi vedere sul suo utilizzo e sulle sue prestazioni qui: https://www.youtube.com/watch?v=4P_kbF0EbZM


1
@NathanOliver: stoie i suoi amici (le conversioni menzionate nella domanda) non funzionano con le versioni locali, quindi non è uno spettacolo.
Pete Becker,

9

Non guadagneresti molto perché in un'espressione simile

int x = sto("1");

Non esiste un modo (semplice) per dedurre il tipo desiderato per il parametro template. Dovresti scrivere

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

che in qualche misura sconfigge lo scopo di fornire una funzione generica. D'altra parte, a

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

sarebbe utile come ti sei reso conto. In C ++ 17 c'è std::from_chars, che fa più o meno esattamente quello (non è un modello ma un insieme di sovraccarichi e prende puntatori ai caratteri anziché a una stringa, ma sono solo dettagli minori).

PS Non esiste un modo semplice per dedurre il tipo desiderato nell'espressione sopra, ma esiste un modo. Non penso che il nocciolo della tua domanda sia esattamente la firma che hai chiesto, e non credo che il seguente sia un buon modo per implementarlo, ma sapevo che esiste un modo per compilare la int x = sto("1");compilazione sopra ed ero curioso di vederlo in azione.

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

Funziona come previsto, ma ha gravi svantaggi, forse soprattutto consente di scrivere auto x = sto(s);, cioè è facile da usare in modo errato.


Penso che fare affidamento sulla conversione implicita qui sia una buona idea. Cercare di disabilitare l'auto è comunque un problema. In genere, l'ho visto inserendo un riferimento const privato in una classe che è inizializzata solo con metodi validi. Non riesco a vedere come si potrebbe sfruttare questo qui, perché dobbiamo costruire un intero oggetto convertitore prima di procedere. Hmmm ....
bremen_matt il

Riesco a vedere il valore nonostante il parametro del tipo non dedotto - come dice la domanda, la motivazione è quella di poter usare dall'interno del codice del modello, dove stai convertendo in un tipo che varia tra le istanze.
Toby Speight,

Qual è il problema fondamentale con auto x = sto(s)? Questa particolare implementazione si interrompe perché converter::xè un riferimento che non rientra nell'ambito di applicazione, ma è risolvibile. Basta rimuovere il riferimento e fare affidamento sulla std::stringsemantica di spostamento.
Salti del

@MSalters sì, era il riferimento che pensavo fosse problematico, ma hai ragione, non è necessario utilizzare un riferimento. Ciò che in realtà mi disturba di più è che sembra essere una funzione, ma la funzionalità effettiva è presente converter, inoltre non sono sicuro che l'uso di un operatore di conversione di template sia stata la scelta migliore, cose che potrebbero essere risolte. Forse non è poi così male come pensavo inizialmente
idclev 463035818

Non credo ci sia alcun problema con il riferimento const qui. La mia comprensione è che il riferimento const manterrà la durata della stringa fino alla distruzione del convertitore ( herbsutter.com/2008/01/01/… )
bremen_matt


3

Nelle versioni precedenti di C ++, stringstream è tuo amico. Se ho capito bene, allora quanto segue potrebbe essere interessante per te. È 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;
}

Questo metodo funziona in C ++ 11 ed è piuttosto generale. Nella mia esperienza, questo metodo è robusto, ma non il più performante.


Sì, questo è quello che ho usato, ma le prestazioni sono sotto funzioni nominate che a volte non sono desiderate
Mircea Ispas
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.