C ++ 'da sayısal için şablon dostu dize


48

C ++ standart kitaplığında, dizeden sayısal türlere dönüştürme işlevleri vardır:

stoi
stol
stoll
stoul
stoull
stof
stod
stold

ancak bunları şablon kodunda kullanmak sıkıcı buluyorum. Neden hiçbir şablon işlevi yoktur:

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

dizeleri sayısal türlere dönüştürmek için?

Onlara sahip olmamak için teknik bir neden göremiyorum, ama belki bir şeyleri kaçırıyorum. Temelde adlandırılmış işlevleri çağırmak ve sayısal olmayan türleri devre dışı bırakmak için enable_if/ öğesini kullanmak üzere uzmanlaşabilirler concepts.

Dizeyi sayısal türlere ve başka bir şekilde verimli bir şekilde dönüştürmek için standart kitaplıkta şablon dostu alternatifler var mı?



1
@Boiethios gerçekten değil - bu sorunun yanıtları "neden" in arkasındaki mantığı açıklıyor, ancak kabul edilen cevap gibi pratik çözümlerle gelmiyorlar. Soruma ihtiyacım olanı daha iyi bir duruma sormak için düzenledim
Mircea Ispas

Yanıtlar:


40

Neden hiçbir şablon işlevi yoktur:

C ++ 17, sayı işlevine böyle genel bir dize sahiptir, ancak farklı adlandırılmıştır. std::from_charsTüm sayısal tipler için aşırı yüklenmiş gittiler .

Gördüğünüz gibi, ilk aşırı yük herhangi bir tamsayı türünü çıkış parametresi olarak alıyor ve mümkünse değeri atayacaktır.

Bu şekilde kullanılabilir:

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

Gördüğünüz gibi, genel bağlamda çalışabilir.



1
Elbette! Hatta basit yapılarla çalışır veya doğru arayüz verilirse sınıflar da çalışır.
Guillaume Racicot

13

Bu bir şablon değildir ve yerel ayarlarla çalışmaz, ancak bu bir gösteri durdurucusu değilse C ++ 17 zaten istediğiniz şeye sahiptir: std::from_chars

Tüm tamsayı ve kayan nokta türleri için aşırı yükler vardır ve sırasıyla tamsayı ve kayan nokta türleri için farklı olan son parametreler hariç arayüz aynıdır (ancak varsayılan değer iyi ise, o zaman yapmanız gerekmez herhangi bir şeyi değiştir). Bu, yerel ayarlara duyarlı bir işlev olmadığı için oldukça hızlıdır. Değer dönüştürme fonksiyonuna diğer dizelerden herhangi birini yenecek ve genellikle büyüklük sıralarına göre olacaktır.

Hakkında çok iyi bir CPPCON videosu var <charconv>(başlıkfrom_charsStephan T. Lavavej'in kullanımı ve performansı hakkında buradan izleyebileceğiniz https://www.youtube.com/watch?v=4P_kbF0EbZM)


1
@NathanOliver: stoive arkadaşları (soruda belirtilen dönüşümler) yerel ayarlarla da çalışmaz, bu yüzden bir gösterici değildir.
Pete Becker

9

Çok kazanamazsınız çünkü

int x = sto("1");

Template parametresi için istenen türü çıkarmanın (kolay) bir yolu yoktur. Yazmak zorunda kalacaksın

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

ki bu bir dereceye kadar genel bir işlev sağlama amacını bozar. Öte yandan,

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

farkettiğiniz gibi faydalı olacaktır. C ++ 17'destd::from_chars , az çok tam olarak bunu yapar (bir şablon değil, aşırı yükler kümesidir ve bir dize yerine karakterlere işaretçiler alır, ancak bu sadece küçük detaylar).

PS Yukarıdaki ifadede istenen tipin çıkarılmasının kolay bir yolu yoktur, ancak bir yolu vardır. Sorunuzun özünün tam olarak istediğiniz imza olduğunu düşünmüyorum ve aşağıdakilerin bunu uygulamak için iyi bir yol olduğunu düşünmüyorum, ancak yukarıdaki int x = sto("1");derlemeyi yapmanın bir yolu olduğunu biliyordum ve görmeyi merak ettim eylem.

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

Bu amaçlandığı gibi çalışır, ancak ciddi dezavantajları vardır, belki de en önemlisi yazmaya izin verir auto x = sto(s);, yani yanlış kullanımı kolaydır.


Burada örtük dönüşüme güvenmek iyi bir fikir. Otomatik devre dışı bırakmaya çalışmak bir sorun. Tipik olarak, sadece geçerli yöntemlerle başlatılan bir sınıfa özel const referansı koyarak yapıldığını gördüm. Nasıl olsa bu kaldıraç göremiyorum çünkü devam etmeden önce bir bütün dönüştürücü nesne inşa etmek zorundayız. Hmmm ....
bremen_matt

Çıkartılmamış tip parametresine rağmen değeri görebiliyorum - sorunun dediği gibi, motivasyon örnek kodlar arasında değişen bir türe dönüştürdüğünüz şablon kodundan kullanabilmektir.
Toby Speight

Temel sorun nedir auto x = sto(s)? Bu özel uygulama converter::x, kapsam dışı kalan bir referans olduğu için kesiliyor , ancak bu düzeltilebilir. Sadece referansı kaldırın ve std::stringhareket semantiğine güvenin.
MSalters

@ MSalters evet, sorunlu olduğunu düşündüğüm referanstı, ama haklısın, referans kullanmaya gerek yok. Aslında beni daha fazla rahatsız eden bir işlev gibi görünüyor ama gerçek işlevsellik olduğunu converter, ayrıca bir şablon dönüşüm operatörü kullanarak en iyi seçim, sabit şeyler olabilir emin değilim. Belki başlangıçta düşündüğüm kadar kötü değil
idclev 463035818

Burada const referansı ile ilgili herhangi bir sorun olduğunu düşünmüyorum. Anladığım kadarıyla, const referansı, dönüştürücü bozulana kadar dizenin ömrünü koruyacaktır ( otlarutter.com/2008/01/01/… )
bremen_matt


3

Eski C ++ sürümlerinde, stringstream arkadaşınızdır. Doğru anlarsam, aşağıdakiler sizin için ilginç olabilir. C ++ 11'dir.

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

Bu yöntem C ++ 11'de çalışır ve oldukça geneldir. Deneyimlerime göre, bu yöntem sağlamdır, ancak en performanslı değildir.


Evet, kullandığım şey bu, ancak performans bazen istenmeyen adlandırılmış işlevlerin altında
Mircea Ispas
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.