Templat string ramah ke numerik dalam C ++

48

Di pustaka standar C ++ ada fungsi untuk mengkonversi dari string ke tipe numerik:

stoi
stol
stoll
stoul
stoull
stof
stod
stold

tapi saya merasa membosankan untuk menggunakannya dalam kode templat. Mengapa tidak ada fungsi template seperti:

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

untuk mengubah string ke tipe numerik?

Saya tidak melihat alasan teknis untuk tidak memilikinya, tetapi mungkin saya kehilangan sesuatu. Mereka dapat dikhususkan untuk memanggil fungsi-fungsi bernama yang mendasari dan menggunakan enable_if/ conceptsuntuk menonaktifkan tipe non numerik.

Apakah ada alternatif ramah template di perpustakaan standar untuk mengkonversi string ke tipe numerik dan sebaliknya dengan cara yang efisien?

Mircea Ispas
sumber
Apakah ini menjawab pertanyaan Anda? Mengapa seri `std :: sto` ... bukan templat?
Boiethios
1
@Boiethios tidak benar-benar - jawaban dari pertanyaan itu menjelaskan alasan di balik "mengapa", tetapi mereka tidak datang dengan solusi praktis seperti jawaban yang diterima. Saya telah mengedit pertanyaan saya untuk meminta alternatif agar lebih baik menyatakan apa yang saya butuhkan
Mircea Ispas

Jawaban:

40

Mengapa tidak ada fungsi template seperti:

C ++ 17 memiliki fungsi string to number yang umum, tetapi dinamai secara berbeda. Mereka pergi std::from_chars, yang kelebihan beban untuk semua jenis numerik.

Seperti yang Anda lihat, overload pertama adalah mengambil semua tipe integer sebagai parameter output dan akan memberikan nilai kepadanya jika memungkinkan.

Dapat digunakan seperti ini:

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

Seperti yang Anda lihat, ini dapat bekerja dalam konteks umum.

Guillaume Racicot
sumber
5
C ++ telah mengalami restrukturisasi sekarang? : o Deklarasi mengikat terstruktur
Alexander - Reinstate Monica
1
Tentu saja! Bahkan bekerja dengan struct sederhana atau jika diberi antarmuka yang tepat, kelas juga.
Guillaume Racicot
13

Ini bukan templat, dan tidak berfungsi dengan lokal tetapi jika itu bukan penghenti acara, maka C ++ 17 sudah memiliki yang Anda inginkan: std::from_chars

Ada kelebihan untuk semua tipe integer dan floating-point dan antarmuka adalah sama kecuali untuk parameter terakhir yang berbeda untuk tipe integer dan floating-point masing-masing (tetapi jika defaultnya baik-baik saja maka Anda tidak perlu ubah apapun). Karena ini bukan fungsi sadar-lokal, ia juga cukup cepat. Ini akan mengalahkan salah satu dari string lain untuk menilai fungsi konversi dan umumnya dengan perintah besarnya.

Ada video CPPCON yang sangat bagus tentang <charconv>( tandanya kepala from_chars) oleh Stephan T. Lavavej yang dapat Anda tonton tentang penggunaan dan kinerjanya di sini: https://www.youtube.com/watch?v=4P_kbF0EbZM

NathanOliver
sumber
1
@NathanOliver: stoidan teman-temannya (konversi yang disebutkan dalam pertanyaan) juga tidak berfungsi dengan lokal, jadi itu bukan showstopper.
Pete Becker
9

Anda tidak akan mendapatkan banyak karena dalam ekspresi suka

int x = sto("1");

Tidak ada cara (mudah) untuk menyimpulkan tipe yang diinginkan untuk parameter template. Anda harus menulis

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

yang untuk beberapa memperpanjang kekalahan tujuan menyediakan fungsi generik. Di sisi lain, a

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

akan bermanfaat jika Anda menyadari. Di C ++ 17 ada std::from_chars, yang melakukan lebih atau kurang persis itu (itu bukan template tapi satu set kelebihan dan dibutuhkan pointer ke karakter bukan string, tapi itu hanya detail kecil).

PS Tidak ada cara mudah untuk menyimpulkan jenis yang diinginkan dalam ekspresi di atas, tetapi ada cara. Saya tidak berpikir inti dari pertanyaan Anda persis tanda tangan yang Anda minta, dan saya tidak berpikir berikut ini adalah cara yang baik untuk mengimplementasikannya, tetapi saya tahu bahwa ada cara untuk membuat int x = sto("1");kompilasi di atas dan saya ingin melihatnya dalam aksi.

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

Ini berfungsi sebagaimana dimaksud, tetapi memiliki kelemahan parah, mungkin yang paling penting itu memungkinkan untuk menulis auto x = sto(s);, yaitu mudah digunakan salah.

idclev 463035818
sumber
Saya pikir mengandalkan konversi implisit di sini adalah ide yang bagus. Mencoba menonaktifkan otomatis adalah masalah. Biasanya, saya telah melihat bahwa dilakukan dengan meletakkan referensi const pribadi di kelas yang hanya diinisialisasi dengan metode yang valid. Saya tidak dapat melihat bagaimana seseorang akan memanfaatkan itu di sini karena kita harus membangun objek konverter keseluruhan sebelum melanjutkan. Hmmm ....
bremen_matt
Saya dapat melihat nilai meskipun parameter tipe yang tidak dideduksi - seperti pertanyaannya mengatakan, motivasi untuk dapat digunakan dari dalam kode templat, di mana Anda mengonversi ke jenis yang bervariasi di antara instansiasi.
Toby Speight
Apa masalah mendasarnya auto x = sto(s)? Implementasi khusus ini rusak karena converter::xmerupakan referensi yang keluar dari ruang lingkup, tapi itu bisa diperbaiki. Hapus saja referensi dan andalkan std::stringsemantik bergerak.
MSalters
@ Pemalas ya, itu adalah referensi yang saya pikir bermasalah, tetapi Anda benar, tidak perlu menggunakan referensi. Apa yang sebenarnya lebih mengganggu saya adalah bahwa hal itu tampaknya merupakan fungsi tetapi fungsi sebenarnya ada converter, juga saya tidak yakin apakah menggunakan operator konversi template adalah pilihan terbaik, hal-hal yang dapat diperbaiki. Mungkin itu tidak seburuk yang saya pikir sebelumnya
idclev 463035818
Saya tidak berpikir ada masalah dengan referensi const di sini. Pemahaman saya adalah bahwa referensi const akan mempertahankan masa pakai string hingga konverternya dihancurkan ( herbalutter.com/2008/01/01/... )
bremen_matt
5

Solusi yang kompatibel dengan semua (bahkan kompiler C ++ yang lebih tua seperti C ++ - 98 yang) adalah dengan menggunakan boost :: lexical_cast yang merupakan template untuk mengkonversi antara tipe numerik dan tipe string dalam kedua cara.

Contoh:

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

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

Łukasz Ślusarczyk
sumber
3

Pada versi C ++ yang lebih lama, stringstream adalah teman Anda. Jika saya mengerti dengan benar, maka hal berikut ini mungkin menarik bagi Anda. Ini adalah 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;
}

Metode ini bekerja di C ++ 11 dan cukup umum. Dalam pengalaman saya, metode ini kuat, tetapi bukan yang paling performan.

bremen_matt
sumber
Ya, inilah yang saya gunakan, tetapi kinerjanya di bawah ini dinamai fungsi-fungsi yang terkadang tidak diinginkan
Mircea Ispas