แม่แบบสตริงที่เป็นมิตรกับตัวเลขใน C ++


48

ในไลบรารีมาตรฐาน C ++ มีฟังก์ชั่นการแปลงจากสตริงเป็นชนิดตัวเลข:

stoi
stol
stoll
stoul
stoull
stof
stod
stold

แต่ฉันคิดว่ามันน่าเบื่อที่จะใช้มันในเทมเพลตโค้ด เหตุใดจึงไม่มีเทมเพลตทำหน้าที่คล้าย:

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

แปลงสตริงเป็นชนิดตัวเลขได้อย่างไร

ฉันไม่เห็นเหตุผลทางเทคนิคใด ๆ ที่จะไม่มี แต่อาจมีบางอย่างที่ขาดหายไป พวกเขาสามารถเป็นพิเศษในการเรียกฟังก์ชั่นที่มีชื่อพื้นฐานและใช้enable_if/ conceptsเพื่อปิดการใช้งานที่ไม่ใช่ตัวเลข

มีทางเลือกที่เป็นมิตรกับแม่แบบในไลบรารีมาตรฐานเพื่อแปลงสตริงเป็นชนิดตัวเลขและวิธีอื่น ๆ อย่างมีประสิทธิภาพหรือไม่?


สิ่งนี้ตอบคำถามของคุณหรือไม่ ทำไม `std :: sto` ... series ไม่ใช่เทมเพลต
Boiethios

1
@Boiethios ไม่จริง - คำตอบจากคำถามนั้นอธิบายถึงเหตุผลเบื้องหลัง "ทำไม" แต่พวกเขาไม่ได้มาพร้อมวิธีแก้ปัญหาการปฏิบัติเช่นคำตอบที่ยอมรับ ฉันได้แก้ไขคำถามของฉันเพื่อขอทางเลือกเพื่อระบุสิ่งที่ดีกว่าที่ฉันต้องการ
Mircea Ispas

คำตอบ:


40

เหตุใดจึงไม่มีเทมเพลตทำหน้าที่คล้าย:

C ++ 17 มีฟังก์ชั่นสตริงสามัญจำนวนดังกล่าว แต่ชื่อแตกต่างกัน พวกเขาไปด้วยstd::from_charsซึ่งมีมากเกินไปสำหรับทุกประเภทที่เป็นตัวเลข

อย่างที่คุณเห็นการโอเวอร์โหลดแรกคือการใช้จำนวนเต็มใด ๆ เป็นพารามิเตอร์เอาต์พุตและจะกำหนดค่าให้กับมันถ้าเป็นไปได้

มันสามารถใช้เช่นนี้:

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

อย่างที่คุณเห็นมันสามารถทำงานได้ในบริบททั่วไป


5
C ++ มีการทำลายล้างตอนนี้หรือไม่ : o ประกาศโครงสร้างที่มีผลผูกพัน
Alexander - Reinstate Monica

1
แน่นอน! มันยังทำงานได้กับโครงสร้างอย่างง่ายหรือถ้าได้รับส่วนต่อประสานที่เหมาะสมคลาสก็เช่นกัน
Guillaume Racicot

13

มันไม่ใช่เทมเพลตและใช้ไม่ได้กับโลแคล แต่ถ้านั่นไม่ใช่ตัวอุดกั้นการแสดง C ++ 17 มีสิ่งที่คุณต้องการอยู่แล้ว: std::from_chars

มีโอเวอร์โหลดสำหรับประเภทจำนวนเต็มและทศนิยมทั้งหมดและอินเทอร์เฟซเหมือนกันยกเว้นพารามิเตอร์สุดท้ายที่แตกต่างกันสำหรับประเภทจำนวนเต็มและทศนิยมตามลำดับ (แต่ถ้าค่าเริ่มต้นดีแล้วคุณไม่จำเป็นต้อง เปลี่ยนอะไรก็ได้) เนื่องจากนี่ไม่ใช่ฟังก์ชั่นที่รับรู้ถึงตำแหน่งจึงค่อนข้างเร็ว มันจะชนะฟังก์ชั่นการแปลงค่าอื่น ๆ ของสตริงและโดยทั่วไปจะเป็นตามลำดับความสำคัญ

มีวิดีโอ CPPCON ที่ดีมากเกี่ยวกับ<charconv>(ส่วนหัวfrom_charsอาศัยอยู่) โดย Stephan T. Lavavej ที่คุณสามารถดูเกี่ยวกับการใช้งานและประสิทธิภาพที่นี่: https://www.youtube.com/watch?v=4P_kbF0EbZM


1
@NathanOliver: stoiและเพื่อน ๆ (การแปลงที่กล่าวถึงในคำถาม) ก็ไม่ได้ทำงานกับตำแหน่งที่ตั้งดังนั้นนั่นไม่ใช่ showstopper
Pete Becker

9

คุณจะไม่ได้รับมากเพราะในการแสดงออกเช่น

int x = sto("1");

ไม่มีวิธี (ง่าย) ในการอนุมานประเภทที่ต้องการสำหรับพารามิเตอร์เทมเพลต คุณจะต้องเขียน

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

ซึ่งบางคนขยายเอาชนะความมุ่งหมายของการให้ฟังก์ชั่นทั่วไป ในทางกลับกันก

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

จะเป็นการใช้งานที่ดีตามที่คุณรับรู้ ใน C ++ 17 มีstd::from_charsซึ่งทำสิ่งนั้นอย่างมากหรือน้อยกว่านั้น (ไม่ใช่เทมเพลต แต่เป็นชุดของโอเวอร์โหลดและใช้พอยน์เตอร์ไปยังตัวอักษรแทนที่จะเป็นสตริง แต่นั่นเป็นเพียงรายละเอียดเล็กน้อยเท่านั้น)

ป.ล. ไม่มีวิธีที่ง่ายในการอนุมานประเภทที่ต้องการในนิพจน์ด้านบน แต่มีวิธี ฉันไม่คิดว่าแก่นแท้ของคำถามของคุณเป็นลายเซ็นที่คุณถามและฉันไม่คิดว่าสิ่งต่อไปนี้เป็นวิธีที่ดีในการนำไปใช้ แต่ฉันรู้ว่ามีวิธีในการint x = sto("1");คอมไพล์ข้างต้นและฉันอยากรู้อยากเห็น ในการดำเนินการ

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

มันใช้งานได้ตามที่ตั้งใจ แต่มันมีข้อเสียที่รุนแรงบางทีที่สำคัญที่สุดคือช่วยให้สามารถเขียนauto x = sto(s);ได้เช่นมันใช้งานง่ายผิด


ฉันคิดว่าการพึ่งพาการแปลงโดยนัยที่นี่เป็นความคิดที่ดี การพยายามปิดการใช้งานอัตโนมัตินั้นเป็นปัญหา โดยทั่วไปแล้วฉันได้เห็นว่าทำได้โดยการอ้างอิง const ส่วนตัวในชั้นเรียนซึ่งจะเริ่มต้นได้เฉพาะวิธีการที่ถูกต้อง ฉันไม่เห็นว่าจะใช้ประโยชน์จากที่นี่ได้อย่างไรเพราะเราต้องสร้างวัตถุตัวแปลงทั้งหมดก่อนที่จะดำเนินการต่อ อืม ....
bremen_matt

ฉันสามารถเห็นคุณค่าแม้จะมีพารามิเตอร์ชนิดที่ไม่อนุมาน - ตามที่คำถามบอกว่าแรงจูงใจคือสามารถใช้จากภายในรหัสเทมเพลตซึ่งคุณแปลงเป็นประเภทที่แตกต่างกันระหว่างอินสแตนซ์
Toby Speight

ปัญหาพื้นฐานauto x = sto(s)คืออะไร การใช้งานเฉพาะช่วงนี้หยุดชะงักเนื่องจากconverter::xเป็นการอ้างอิงที่ไม่ได้อยู่ในขอบเขต แต่สามารถแก้ไขได้ เพียงลบการอ้างอิงและพึ่งพาซีแมนทิกstd::stringส์ย้าย
MSalters

@MSters ใช่มันเป็นข้อมูลอ้างอิงที่ฉันคิดว่าเป็นปัญหา แต่คุณพูดถูกไม่จำเป็นต้องใช้ข้อมูลอ้างอิง สิ่งที่รบกวนฉันมากขึ้นก็คือดูเหมือนว่ามันจะเป็นฟังก์ชั่น แต่มีฟังก์ชั่นการใช้งานจริงconverterและฉันก็ไม่แน่ใจว่าหากใช้ตัวดำเนินการแปลงเทมเพลตเป็นตัวเลือกที่ดีที่สุดสิ่งที่สามารถแก้ไขได้ อาจจะไม่เลวร้ายอย่างที่ฉันคิดตอนแรก
idclev 463035818

ฉันไม่คิดว่าจะมีปัญหากับการอ้างอิง const ที่นี่ ความเข้าใจของฉันคือการอ้างอิง const จะรักษาอายุการใช้งานของสตริงจนกว่าตัวแปลงจะถูกทำลาย ( herbutter.com/2008/01/01/… )
bremen_matt

5

โซลูชันที่เข้ากันได้กับคอมไพเลอร์ C ++ ที่เก่ากว่าเช่น C ++ - 98 รายการคือการใช้boost :: lexical_castซึ่งเป็นเทมเพลตสำหรับการแปลงระหว่างชนิดตัวเลขและสตริงในทั้งสองวิธี

ตัวอย่าง:

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

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


3

ในเวอร์ชัน C ++ ที่เก่ากว่า stringstream คือเพื่อนของคุณ หากฉันเข้าใจถูกต้องแล้วสิ่งต่อไปนี้น่าสนใจสำหรับคุณ มันคือ 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;
}

วิธีนี้ใช้ได้ใน C ++ 11 และค่อนข้างทั่วไป จากประสบการณ์ของฉันวิธีนี้มีประสิทธิภาพ แต่ไม่ใช่ผู้มีประสิทธิภาพมากที่สุด


ใช่นี่คือสิ่งที่ฉันใช้ แต่ประสิทธิภาพต่ำกว่าชื่อฟังก์ชั่นซึ่งบางครั้งก็ไม่ต้องการ
Mircea Ispas
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.