Ciąg przyjazny dla szablonu na numeryczny w C ++

48

W standardowej bibliotece C ++ są funkcje do konwersji z ciągów znaków na typy numeryczne:

stoi
stol
stoll
stoul
stoull
stof
stod
stold

ale uważam, że nużące jest używanie ich w kodzie szablonu. Dlaczego nie ma funkcji szablonów, takich jak:

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

konwertować ciągi znaków na typy numeryczne?

Nie widzę żadnego technicznego powodu, aby ich nie mieć, ale może coś mi brakuje. Mogą specjalizować się w wywoływaniu bazowych nazwanych funkcji i używaniu enable_if/ conceptsdo wyłączania typów nienumerycznych.

Czy w standardowej bibliotece są jakieś przyjazne szablony alternatywne do konwertowania napisów na typy numeryczne i na odwrót w efektywny sposób?

Mircea Ispas
źródło
Czy to odpowiada na twoje pytanie? Dlaczego seria `std :: sto` ... nie jest szablonem?
Boiethios,
1
@Boiethios niezupełnie - odpowiedzi z tego pytania wyjaśniają uzasadnienie „dlaczego”, ale nie zawierają praktycznych rozwiązań, takich jak zaakceptowana odpowiedź. Zredagowałem swoje pytanie, aby poprosić o alternatywę, aby lepiej określić, czego potrzebuję
Mircea Ispas

Odpowiedzi:

40

Dlaczego nie ma funkcji szablonów, takich jak:

C ++ 17 ma taką ogólną funkcję ciągu na liczbę, ale ma inną nazwę. Oni poszli z std::from_chars, który jest przeciążony dla wszystkich typów numerycznych.

Jak widać, pierwsze przeciążenie przyjmuje dowolny typ liczb całkowitych jako parametr wyjściowy i przypisze mu wartość, jeśli to możliwe.

Można go użyć w następujący sposób:

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

Jak widać, może działać w ogólnym kontekście.

Racilla Guillaume
źródło
5
C ++ ma teraz restrukturyzację? : o Ustrukturyzowana wiążąca deklaracja
Aleksander - Przywróć Monikę
1
Oczywiście! Działa nawet z prostymi strukturami lub, jeśli ma odpowiedni interfejs, także klasy.
Guillaume Racicot
13

To nie jest szablon i nie działa z ustawieniami narodowymi, ale jeśli nie jest to ogranicznik programu, C ++ 17 ma już to, czego chcesz: std::from_chars

Występują przeciążenia dla wszystkich typów liczb całkowitych i zmiennoprzecinkowych, a interfejs jest taki sam, z wyjątkiem ostatnich parametrów, które są różne dla typów całkowitych i zmiennoprzecinkowych (ale jeśli wartość domyślna jest w porządku, nie trzeba zmienić cokolwiek). Ponieważ nie jest to funkcja rozpoznająca ustawienia regionalne, jest również dość szybka. Uderzy w inną funkcję konwersji łańcucha na wartość i generalnie jest o rząd wielkości.

Jest bardzo dobry film CPPCON o <charconv>(nagłówek from_charsmieszka) Stephana T. Lavaveja, który można obejrzeć na temat jego użycia i wydajności tutaj: https://www.youtube.com/watch?v=4P_kbF0EbZM

NathanOliver
źródło
1
@NathanOliver: stoii jego przyjaciele (konwersje wspomniane w pytaniu) również nie działają z ustawieniami regionalnymi, więc nie jest to showstopper.
Pete Becker
9

Nie zyskałbyś wiele, ponieważ w takim wyrażeniu jak

int x = sto("1");

Nie ma (łatwego) sposobu, aby wydedukować pożądany typ parametru szablonu. Musisz pisać

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

co w pewnym stopniu przeczy celowi zapewnienia funkcji ogólnej. Z drugiej strony, a

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

przyda ci się, jak sobie uświadomiłeś. W C ++ 17 jest to std::from_chars, co robi mniej więcej dokładnie to (nie jest to szablon, ale zestaw przeciążeń i wymaga wskaźników do znaków zamiast łańcucha, ale to tylko drobne szczegóły).

PS Nie ma łatwego sposobu, aby wydedukować pożądany typ w powyższym wyrażeniu, ale istnieje sposób. Nie sądzę, aby rdzeń twojego pytania był dokładnie podpisem, o który prosiłeś, i nie sądzę, że poniższy sposób jest dobrym sposobem na jego wdrożenie, ale wiedziałem, że istnieje sposób na int x = sto("1");skompilowanie powyższego i byłem ciekawy to zobaczyć w akcji.

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

Działa to zgodnie z przeznaczeniem, ale ma poważne wady, być może co najważniejsze pozwala na pisanie auto x = sto(s);, tzn. Jest łatwe w użyciu.

idclev 463035818
źródło
Myślę, że opieranie się tutaj na domniemanej konwersji jest dobrym pomysłem. Problemem jest jednak próba wyłączenia auto. Zazwyczaj widziałem to poprzez umieszczenie prywatnego odwołania do stałej w klasie, która jest inicjowana tylko za pomocą prawidłowych metod. Nie widzę jednak, jak można to tutaj wykorzystać, ponieważ przed kontynuowaniem musimy skonstruować cały obiekt konwertera. Hmmm ....
bremen_matt
Widzę wartość pomimo niededukowanego parametru typu - jak mówi pytanie, motywacją jest możliwość użycia z poziomu kodu szablonu, w którym konwertujesz na typ, który zmienia się między instancjami.
Toby Speight
Z czym jest podstawowy problem auto x = sto(s)? Ta konkretna implementacja psuje się, ponieważ converter::xjest to odwołanie, które wykracza poza zakres, ale można to naprawić. Wystarczy usunąć odniesienie i polegać na std::stringsemantyce przenoszenia.
MSalters
@MSalters tak, to było odniesienie, które uważałem za problematyczne, ale masz rację, nie musisz używać odniesienia. Najbardziej przeszkadza mi to, że wydaje się być funkcją, ale rzeczywista funkcjonalność jest dostępna converter, nie jestem również pewien, czy użycie operatora konwersji szablonu było najlepszym wyborem, co można naprawić. Może nie jest tak źle, jak początkowo myślałem
idclev 463035818
Nie sądzę, żeby tutaj był problem z odniesieniem do const. Rozumiem, że stałe odwołanie zachowa żywotność łańcucha, dopóki konwerter nie zostanie zniszczony ( herbutter.com/2008/01/01/… )
bremen_matt
5

Należy używać rozwiązania kompatybilnego ze wszystkimi (nawet starszymi kompilatorami C ++, takimi jak C ++ - 98) boost :: lexical_cast, który jest szablonem do konwersji między typem liczbowym a ciągiem znaków na dwa sposoby.

Przykład:

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

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

Łukasz Ślusarczyk
źródło
3

W starszych wersjach C ++ stringstream jest twoim przyjacielem. Jeśli dobrze rozumiem, poniższe informacje mogą być dla Ciebie interesujące. Jest to 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;
}

Ta metoda działa w C ++ 11 i jest dość ogólna. Z mojego doświadczenia wynika, że ​​ta metoda jest solidna, ale nie najbardziej wydajna.

bremen_matt
źródło
Tak, tego właśnie użyłem, ale wydajność jest poniżej nazwanych funkcji, które czasem nie są pożądane
Mircea Ispas