Mẫu chuỗi thân thiện thành số trong C ++


48

Trong thư viện chuẩn C ++, có các hàm để chuyển đổi từ chuỗi thành kiểu số:

stoi
stol
stoll
stoul
stoull
stof
stod
stold

nhưng tôi thấy tẻ nhạt khi sử dụng chúng trong mã mẫu. Tại sao không có chức năng mẫu nào đó như:

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

để chuyển đổi chuỗi thành loại số?

Tôi không thấy bất kỳ lý do kỹ thuật nào để không có chúng, nhưng có lẽ tôi đang thiếu thứ gì đó. Chúng có thể được chuyên môn hóa để gọi các hàm được đặt tên cơ bản và sử dụng enable_if/ conceptsđể vô hiệu hóa các loại không số.

Có bất kỳ lựa chọn thay thế thân thiện mẫu nào trong thư viện tiêu chuẩn để chuyển đổi chuỗi thành các kiểu số và ngược lại một cách hiệu quả không?


Điều này có trả lời câu hỏi của bạn không? Tại sao loạt `std :: sto` ... không phải là mẫu?
Boiethios

1
@Boiethios không thực sự - câu trả lời từ câu hỏi đó giải thích lý do căn bản đằng sau "tại sao", nhưng chúng không đi kèm với các giải pháp thực tế như câu trả lời được chấp nhận. Tôi đã chỉnh sửa câu hỏi của mình để yêu cầu thay thế để nêu rõ hơn những gì tôi cần
Mircea Ispas

Câu trả lời:


40

Tại sao không có chức năng mẫu nào đó như:

C ++ 17 có chuỗi chung như vậy với hàm số, nhưng được đặt tên khác nhau. Họ đã đi với std::from_chars, đó là quá tải cho tất cả các loại số.

Như bạn có thể thấy, quá tải đầu tiên là lấy bất kỳ loại số nguyên nào làm tham số đầu ra và sẽ gán giá trị cho nó nếu có thể.

Nó có thể được sử dụng như thế này:

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

Như bạn có thể thấy, nó có thể hoạt động trong bối cảnh chung.



1
Tất nhiên! Nó thậm chí hoạt động với các cấu trúc đơn giản hoặc nếu được cung cấp giao diện phù hợp, các lớp cũng vậy.
Trường đua Guillaume

13

Đây không phải là một mẫu và nó không hoạt động với các ngôn ngữ nhưng nếu đó không phải là một công cụ chặn hiển thị thì C ++ 17 đã có những gì bạn muốn: std::from_chars

Có quá tải cho tất cả các loại số nguyên và dấu phẩy động và giao diện giống nhau ngoại trừ các tham số cuối cùng khác nhau cho các loại số nguyên và dấu phẩy động tương ứng (nhưng nếu mặc định là tốt thì bạn không cần phải thay đổi bất cứ điều gì). Bởi vì đây không phải là chức năng nhận biết miền địa phương nên nó cũng khá nhanh. Nó sẽ đánh bại bất kỳ chuỗi nào khác thành hàm chuyển đổi giá trị và nói chung là theo thứ tự độ lớn.

Có một video CPPCON rất hay về <charconv>(tiêu đề from_charssống trong) của Stephan T. Lavavej mà bạn có thể xem về cách sử dụng và hiệu suất của nó tại đây: https://www.youtube.com/watch?v=4P_kbF0EbZM


1
@NathanOliver: stoivà bạn bè của nó (các chuyển đổi được đề cập trong câu hỏi) cũng không hoạt động với các địa phương, vì vậy đó không phải là một showstopper.
Pete Becker

9

Bạn sẽ không đạt được nhiều vì trong một biểu thức như

int x = sto("1");

Không có cách nào (dễ dàng) để suy ra loại mong muốn cho tham số mẫu. Bạn sẽ phải viết

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

mà một số mở rộng đánh bại mục đích cung cấp một chức năng chung. Mặt khác, một

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

sẽ được sử dụng tốt như bạn nhận ra. Trong C ++ 17 std::from_chars, có ít nhiều chính xác điều đó (nó không phải là khuôn mẫu mà là một tập hợp quá tải và nó đưa con trỏ tới ký tự thay vì một chuỗi, nhưng đó chỉ là chi tiết nhỏ).

PS Không có cách dễ dàng để suy ra loại mong muốn trong biểu thức trên, nhưng có một cách. Tôi không nghĩ cốt lõi câu hỏi của bạn chính xác là chữ ký mà bạn đã yêu cầu và tôi không nghĩ sau đây là một cách tốt để thực hiện nó, nhưng tôi biết rằng có một cách để int x = sto("1");biên dịch ở trên và tôi tò mò muốn xem trong hành động.

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

Điều này hoạt động như dự định, nhưng nó có nhược điểm nghiêm trọng, có lẽ quan trọng nhất là nó cho phép viết auto x = sto(s);, tức là nó dễ sử dụng sai.


Tôi nghĩ rằng dựa vào chuyển đổi ngầm ở đây là một ý tưởng tốt. Cố gắng vô hiệu hóa tự động là một vấn đề mặc dù. Thông thường, tôi đã thấy rằng được thực hiện bằng cách đặt một tham chiếu const private trong một lớp chỉ được khởi tạo bằng các phương thức hợp lệ. Tôi không thể thấy người ta sẽ tận dụng điều đó ở đây như thế nào bởi vì chúng ta phải xây dựng một đối tượng chuyển đổi toàn bộ trước khi tiếp tục. Hmmm ....
bremen_matt

Tôi có thể thấy giá trị mặc dù tham số loại không suy diễn - như câu hỏi nói, động lực là có thể sử dụng từ bên trong mã mẫu, trong đó bạn đang chuyển đổi sang loại khác nhau giữa các lần xuất hiện.
Toby Speight

Vấn đề cơ bản là auto x = sto(s)gì? Việc triển khai cụ thể này bị phá vỡ vì converter::xlà một tài liệu tham khảo nằm ngoài phạm vi, nhưng điều đó có thể sửa được. Chỉ cần xóa tham chiếu và dựa vào std::stringngữ nghĩa di chuyển của.
MSalters

@MSalters có, đó là tài liệu tham khảo mà tôi nghĩ là có vấn đề, nhưng bạn nói đúng, không cần sử dụng tài liệu tham khảo. Điều thực sự làm tôi băn khoăn hơn là nó có vẻ là một chức năng nhưng chức năng thực sự nằm trong đó converter, tôi cũng không chắc nếu sử dụng toán tử chuyển đổi mẫu là lựa chọn tốt nhất, những thứ có thể được sửa. Có lẽ nó không tệ như tôi nghĩ ban đầu
idclev 463035818

Tôi không nghĩ có bất kỳ vấn đề nào với tài liệu tham khảo const ở đây. Hiểu biết của tôi là tham chiếu const sẽ bảo toàn thời gian tồn tại của chuỗi cho đến khi trình chuyển đổi bị hủy ( Herbutter.com/2008/01/01/ Ấn )
bremen_matt

5

Giải pháp tương thích với tất cả (ngay cả các trình biên dịch C ++ cũ hơn như C ++ - 98) là sử dụng boost :: lexical_cast , đây là một mẫu để chuyển đổi giữa các loại số và chuỗi theo cả hai cách.

Thí dụ:

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

Xem: https://www.boost.org/doc/libs/1_42_0/libs/conversion/lexical_cast.htmlm


3

Trên các phiên bản C ++ cũ hơn, chuỗi dòng là bạn của bạn. Nếu tôi hiểu chính xác, thì những điều sau đây có thể thú vị với bạn. Đó là 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;
}

Phương pháp này hoạt động trong C ++ 11 và khá chung chung. Theo kinh nghiệm của tôi, phương pháp này mạnh mẽ, nhưng không hiệu quả nhất.


Vâng, đây là những gì tôi đã sử dụng, nhưng hiệu suất bên dưới các chức năng được đặt tên đôi khi không mong muốn
Mircea Ispas
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.