Форум программистов, компьютерный форум, киберфорум
NullReferenced
Войти
Регистрация
Восстановить пароль

Преобразование строк в C++: std::from_chars от C++17 до C++26

Запись от NullReferenced размещена 15.03.2025 в 20:26
Показов 1848 Комментарии 3

Нажмите на изображение для увеличения
Название: a322ada0-7ec7-4ee3-b03d-ffc9f20e904d.jpg
Просмотров: 64
Размер:	130.0 Кб
ID:	10414
Конвертация строк в числа — задача, с которой сталкивается практически каждый C++ разработчик. Несмотря на кажущуюся простоту, эта операция таит множество подводных камней и неочевидных последствий для производительности и надежности кода. До появления C++17 у нас имелся целый зоопарк различных методов для преобразования текстовых данных в числовые значения. Каждый из них обладал своими особенностями и недостатками:

Функции старого стиля из C предлагали минимальные удобства. Помните atoi(), atof() и их собратьев? Эти функции работают с C-строками и не предоставляют никакой информации об ошибках. Если передать atoi() строку "42abc", она вернет 42 и тихо проигнорирует остаток, что может привести к труднообнаруживаемым багам.

Немного лучше себя ведут функции семейства strtol(), strtod(), которые хотя бы позволяют узнать, где закончилось успешное преобразование. Но и они требуют дополнительного кода для проверки ошибок:

C++
1
2
3
4
5
char* end;
long value = strtol("123abc", &end, 10);
if (*end != '\0') {
    // Обнаружены символы после числа
}
С появлением C++98 мы получили классы потоков. Использование std::stringstream выглядело более элегантно и типобезопасно:

C++
1
2
3
4
5
std::stringstream ss("42");
int value;
if (ss >> value) {
    // Преобразование успешно
}
Но за этой элегантностью скрывается весьма неприятный секрет — каждый вызов конструктора std::stringstream приводит к выделению памяти в динамической памяти. При интенсивном использовании это создает серьезное давление на систему управления памятью и, как следствие, заметно снижает производительность. В C++11 появились такие функции как std::stoi(), std::stod() и т.д., которые принимают строки в стиле C++ и выбрасывают исключения при ошибках:

C++
1
2
3
4
5
6
7
8
9
10
try {
    int value = std::stoi("42");
    // Используем value
}
catch (const std::invalid_argument& e) {
    // Строка не содержит числа
}
catch (const std::out_of_range& e) {
    // Число слишком большое
}
Этот подход решил некоторые проблемы, но создал новые. Исключения — механизм с высокой стоимостью, особенно если они используются для обработки обычного потока управления, а не по-настоящему исключительных ситуаций. К тому же, функции вроде std::stoi() сначала преобразуют данные в промежуточный формат, что неизбежно снижает производительность. Отдельная головная боль — локализованные разделители. Большинство функций зависят от установленной локали, что может привести к неожиданным результатам. Например, в европейской локали десятичный разделитель — запятая, а не точка, что делает преобразование "3.14" в double невозможным без дополнительных манипуляций с локалью. Еще одна проблема — обработка "мусора" в конце строки. Некоторые функции (atoi, std::stoi) просто игнорируют символы после числа, другие (stringstream) могут требовать точного соответствия формату, что затрудняет разбор частичных данных.

И, наконец, все эти методы рассчитаны на работу с нуль-терминированными строками или объектами std::string. Но в реальных приложениях данные часто приходят в буферах, и преобразование их в строку перед разбором — напрасная трата ресурсов.

Комбинация всех этих факторов сделала стандартные методы конвертации текста в числа неудобными для высокопроизводительных приложений. Потребность в более эффективном механизме становилась все более очевидной с ростом требований к производительности современного программного обеспечения.

Появление std::from_chars в C++17



Когда дело доходит до разработки стандарта C++, каждое новое дополнение проходит строгий отбор. Функции преобразования строк оставались практически неизменными на протяжении многих лет, пока в C++17 не был представлен совершенно новый подход — функция std::from_chars. Этот метод был разработан с чистого листа, с прицелом на максимальную производительность и минимальные накладные расходы.

std::from_chars и её дополнение std::to_chars (для обратного преобразования) объединены в новый заголовочный файл <charconv>. Сама функция имеет несколько перегрузок для различных числовых типов. Её основная философия — работать на максимально низком уровне, без лишней "магии" высокоуровневых API. Почему же понадобилось разрабатывать новую функцию, когда уже существует столько способов конвертации? Основные причины кроются в следующих особенностях std::from_chars:
1. Нет выделения памяти. В отличие от std::stringstream, которая создает внутренние буферы, или std::stoi, которая может вызывать преобразования между Unicode-форматами, from_chars работает напрямую с указанным буфером без промежуточных копий.
2. Отсутствие исключений. Вместо того чтобы генерировать исключения при ошибках, функция возвращает структуру с информацией о результате. Это критично для высокопроизводительного кода, где обработка исключений может привести к серьёзным просадкам производительности.
3. Независимость от локали. Функция всегда использует точку как десятичный разделитель и игнорирует любые настройки локали. Это делает ее результаты предсказуемыми и не зависящими от конфигурации среды.
4. Работа с сырыми указателями. Функция принимает указатели на начало и конец области памяти, что позволяет легко использовать ее с любыми источниками данных, а не только с форматированными строками.
5. Высокая информативность о причинах ошибок. Возвращаемая структура from_chars_result содержит детальную информацию о том, что пошло не так, если преобразование не удалось.

Вот как выглядит базовый синтаксис для преобразования строки в целое число:

C++
1
2
3
4
5
6
7
8
9
10
#include <charconv>
#include <string>
 
std::string str = "12345";
int value = 0;
auto result = std::from_chars(str.data(), str.data() + str.size(), value);
 
if (result.ec == std::errc()) {
    // Преобразование успешно, value содержит число 12345
}
А вот пример для чисел с плавающей точкой:

C++
1
2
3
4
5
6
7
8
9
10
#include <charconv>
#include <string>
 
std::string str = "3.14159";
double value = 0.0;
auto result = std::from_chars(str.data(), str.data() + str.size(), value);
 
if (result.ec == std::errc()) {
    // Преобразование успешно, value содержит 3.14159
}
Следует отметить, что в первых реализациях стандарта C++17 поддержка чисел с плавающей точкой в from_chars часто отсутствовала или была неполной. Это связано со сложностью правильного и быстрого преобразования для таких чисел. Полная поддержка плавающей точки стала доступна лишь в более поздних обновлениях компиляторов.

std::from_chars возвращает структуру from_chars_result, которая содержит два поля:
  • ptr: указатель на первый символ, не соответствующий разбираемому шаблону (или указатель на конец буфера, если все символы использованы).
  • ec: код ошибки типа std::errc.

Вот какие значения может принимать поле ec:
  • std::errc() (пустой код) — преобразование прошло успешно.
  • std::errc::invalid_argument — строка не может быть преобразована в указанный тип.
  • std::errc::result_out_of_range — число слишком велико для указанного типа.

Одно из самых мощных преимуществ from_chars — возможность работать с частью строки, без требования точного соответствия. Например:

C++
1
2
3
4
5
6
7
8
const char* data = "42 is the answer";
int value;
auto result = std::from_chars(data, data + strlen(data), value);
 
if (result.ec == std::errc()) {
    // value содержит 42, а result.ptr указывает на ' ' после цифры
    size_t processed = result.ptr - data; // 2 символа обработано
}
Кроме того, для целочисленных типов from_chars поддерживает различные системы счисления через третий параметр:

C++
1
2
3
4
const char* hex = "1A3F";
int value;
auto result = std::from_chars(hex, hex + strlen(hex), value, 16);
// При успехе value будет содержать 6719 (десятичное представление 1A3F в шестнадцатеричной системе)
Для систем с повышенными требованиями к производительности эти особенности делают std::from_chars незаменимым инструментом. Независимые тесты показывают, что этот метод может быть до 4-5 раз быстрее, чем std::stoi, до 2-3 раз быстрее, чем atoi, и почти в 50 раз быстрее, чем std::istringstream.

По данным измерений на различных компиляторах:
  • На GCC from_chars примерно в 4.5 раза быстрее stoi, в 2.2 раза быстрее чем atoi и в почти 50 раз быстрее istringstream.
  • На Clang похожие результаты: в 3.5 раза быстрее stoi, в 2.7 раза быстрее atoi и в 60 раз быстрее istringstream.
  • MSVC также демонстрирует существенное преимущество: около 3 раз быстрее stoi, примерно в 2 раза быстрее atoi и почти в 50 раз быстрее istringstream.

Такой впечатляющий прирост производительности объясняется тем, что функция была спроектирована с нуля для эффективности, без обратной совместимости с устаревшими подходами.

Тит Винтерс, член комитета по стандартизации C++, объяснил основную идею появления std::from_chars следующим образом: "Цель этих API — не в том, чтобы люди использовали их напрямую, а в том, чтобы строить на их основе более интересные и полезные инструменты. Это примитивы, и мы (комитет) считаем, что они должны быть в стандарте, потому что нет эффективного способа выполнять эти операции без вызова внешних функций с нуль-терминированными C-строками."

Этот подход — создание низкоуровневых примитивов, на которых можно строить более высокоуровневые абстракции — хорошо вписывается в общую философию C++. Идея в том, что разработчики библиотек смогут обернуть from_chars в удобные интерфейсы, сохраняя при этом высокую производительность. Важно понимать, что хотя API from_chars может показаться неуклюжим по сравнению с более простыми функциями вроде std::stoi, его главная цель — максимальная эффективность и контроль над процессом. Вместо того чтобы "угадывать", что вам нужно, или делать что-то за кулисами, from_chars делает ровно то, что вы ему указываете, не больше и не меньше. Для лучшего понимания работы с from_chars, вот пример с обработкой потенциальных ошибок:

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <charconv>
#include <iostream>
#include <string>
 
int main() {
    const std::string str = "12345678901234"; // Большое число
    int value = 0;
    auto res = std::from_chars(str.data(), str.data() + str.size(), value);
 
    if (res.ec == std::errc()) {
        std::cout << "Значение: " << value 
                  << ", обработано символов: " << res.ptr - str.data() << '\n';
    } else if (res.ec == std::errc::invalid_argument) {
        std::cout << "Недопустимый аргумент!\n";
    } else if (res.ec == std::errc::result_out_of_range) {
        std::cout << "Число за пределами диапазона! Обработано символов: " 
                  << res.ptr - str.data() << '\n';
    }
}
Этот пример демонстрирует, как from_chars сообщает об ошибках и предоставляет информацию о том, сколько символов было успешно обработано перед возникновением ошибки.

При использовании для чисел с плавающей точкой from_chars поддерживает различные форматы через перечисление std::chars_format:
  • std::chars_format::scientific — научная нотация (например, "1.23e+4").
  • std::chars_format::fixed — фиксированная точка (например, "12345.67").
  • std::chars_format::hex — шестнадцатеричная запись с плавающей точкой (например, "0x1.Ap+3").
  • std::chars_format::general — комбинация научной и фиксированной нотации.

Вот пример использования различных форматов:

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <charconv>
#include <string>
#include <iostream>
 
void try_parse(const std::string& str, std::chars_format fmt) {
    double value = 0;
    auto res = std::from_chars(str.data(), str.data() + str.size(), value, fmt);
    
    if (res.ec == std::errc()) {
        std::cout << "Успех: " << value << '\n';
    } else {
        std::cout << "Ошибка: " << (res.ec == std::errc::invalid_argument ? 
                                 "недопустимый формат" : "вне диапазона") << '\n';
    }
}
 
int main() {
    try_parse("123.456", std::chars_format::fixed);
    try_parse("1.23456e2", std::chars_format::scientific);
    try_parse("123.456", std::chars_format::scientific); // Ошибка
    try_parse("1.23456e2", std::chars_format::fixed);    // Ошибка
    try_parse("1.23456e2", std::chars_format::general);  // OK
}
Заметьте, что режим scientific требует наличия экспоненты, а fixed — её отсутствия. Режим general допускает оба варианта, что делает его наиболее гибким, но потенциально чуть менее производительным.

Еще одним преимуществом from_chars является то, что он не требует нуль-терминированных строк. Функция работает с произвольным диапазоном памяти, заданным двумя указателями, что делает её идеальной для разбора текста, полученного из сетевых протоколов, файлов или других источников данных, где строки могут не быть нуль-терминированными или нам нужно обработать только часть большого буфера.

Преобразование std::string в ptrdiff_t
Возможно ли преобразование std::string в ptrdiff_t? Если да, то напишите, как это сделать, пожалуйста.

Преобразование из std::string - в std::wstring
Как попроще преобразовать string в широкую строку wstring? Так не получается: #include &lt;iostream&gt; #include &lt;string&gt; using...

Запрошено преобразование от ‘const std::string*’ к нескалярному типу ‘std::string’
private: std::string firstName; }; std::string ClientData::getFirstName() const{ return firstName; } Дает в итоге вот такой...

Неправильная работа stod, from_chars, strtod в MSVC
Добрый день. Столкнулся с проблемой и не смог найти решение. В работе программы постоянно просиходит получение / конвертация чисел с плавающей...


Особенности результирующего типа std::from_chars_result



Ключевым элементом механизма std::from_chars является тип возвращаемого значения - структура from_chars_result. Эта структура представляет собой простой, но весьма информативный объект, содержащий всю необходимую информацию о результате выполнения конвертации. Давайте рассмотрим определение этой структуры:

C++
1
2
3
4
struct from_chars_result {
    const char* ptr;
    std::errc ec;
};
Несмотря на простоту, эти два поля дают исчерпывающую информацию о том, что произошло при попытке преобразования строки в число.
Поле ptr указывает на символ, где процесс преобразования остановился. В идеальном случае, когда вся строка была успешно преобразована, этот указатель будет указывать на позицию сразу после последнего обработанного символа (то есть на конец буфера, если вся строка была числом). Если встречается символ, который не может быть интерпретирован как часть числа, то ptr указывает на этот символ. Поле ec (error code) содержит код ошибки, указывающий, почему преобразование завершилось. Существует три возможных состояния:
1. std::errc() — значение по умолчанию, указывающее на успешное преобразование.
2. std::errc::invalid_argument — строка содержит символы, которые не могут быть интерпретированы как число в заданном формате.
3. std::errc::result_out_of_range — число слишком большое или слишком маленькое для целевого типа.

Использование этой структуры дает нам несколько преимуществ по сравнению с традиционными подходами:

Точное определение места ошибки



Если во время преобразования произошла ошибка, мы точно знаем, где она произошла благодаря полю ptr. Это особенно полезно при разборе сложных форматов данных, где числа могут быть перемешаны с другой информацией:

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
const char* input = "position={123,456,789}";
int x, y, z;
const char* p = input;
 
// Ищем открывающую скобку
while (*p && *p != '{') ++p;
if (*p == '{') ++p;
 
// Читаем x
auto res = std::from_chars(p, input + strlen(input), x);
if (res.ec != std::errc()) {
    // Обработка ошибки
    return;
}
p = res.ptr;
 
// Убеждаемся, что следующий символ - запятая
if (*p != ',') {
    // Неверный формат
    return;
}
++p;
 
// Читаем y и z аналогичным образом
// ...
В этом примере мы точно знаем, где закончилось чтение числа x и можем соответствующим образом продолжить разбор строки.

Возможность частичного разбора



Возможность узнать, сколько символов было преобразовано, позволяет реализовать разбор частей составных строк. В комбинации с тем, что функция не генерирует исключения, это делает возможным написание более эффективного кода для разбора форматированных строк:

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const char* input = "42,3.14,hello";
const char* end = input + strlen(input);
const char* current = input;
 
int i_val;
auto res1 = std::from_chars(current, end, i_val);
if (res1.ec == std::errc()) {
    current = res1.ptr + 1; // Пропускаем запятую
}
 
double d_val;
auto res2 = std::from_chars(current, end, d_val);
if (res2.ec == std::errc()) {
    current = res2.ptr + 1; // Пропускаем запятую
}
 
// Теперь current указывает на "hello"

Разделение ошибок на категории



Тип std::errc позволяет чётко разделить разные виды ошибок преобразования. Например, мы можем по-разному реагировать на ситуации, когда строка не содержит допустимого числа вообще (invalid_argument) и когда число слишком велико для выбранного типа (result_out_of_range). Пример, демонстрирующий такое разделение:

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <charconv>
#include <iostream>
 
void parse_and_report(const char* str) {
    long long value;
    auto res = std::from_chars(str, str + strlen(str), value);
    
    if (res.ec == std::errc()) {
        std::cout << "Успех: " << value << '\n';
    } else if (res.ec == std::errc::invalid_argument) {
        std::cout << "Ошибка: строка \"" << str << "\" не содержит числа\n";
    } else if (res.ec == std::errc::result_out_of_range) {
        std::cout << "Ошибка: число в строке \"" << str << "\" слишком велико\n";
    }
}
 
int main() {
    parse_and_report("12345");
    parse_and_report("not a number");
    parse_and_report("999999999999999999999");
}

Отсутствие исключений



В отличие от std::stoi и аналогичных функций, которые выбрасывают исключения при ошибках, std::from_chars просто возвращает структуру с информацией о результате. Это соответствует современным практикам программирования на C++, где исключения рекомендуется использовать только для действительно исключительных ситуаций, а не для рутинной обработки ошибок. Отсутствие исключений также делает функцию более предсказуемой в плане производительности и делает возможным использование в контекстах, где исключения нежелательны или запрещены.

Обратная функциональность: std::to_chars и её взаимодействие с from_chars



Для полноты картины невозможно рассматривать std::from_chars в отрыве от его партнера — функции std::to_chars, которая выполняет обратное преобразование: из числовых типов в текстовое представление. Вместе они образуют сбалансированную пару для высокоэффективной работы с преобразованиями между числами и текстом.

std::to_chars следует тем же принципам, что и from_chars: никакого выделения памяти, никаких исключений, независимость от локали и работа с сырыми указателями. Сигнатура функции выглядит так:

C++
1
std::to_chars_result to_chars(char* first, char* last, TYPE value, int base = 10);
Где TYPE — любой числовой тип (целочисленный или с плавающей точкой).

Функция возвращает структуру to_chars_result, аналогичную from_chars_result:

C++
1
2
3
4
struct to_chars_result {
    char* ptr;
    std::errc ec;
};
Пример использования для целых чисел прост и понятен:

C++
1
2
3
4
5
6
7
char buffer[100];
int value = 12345;
auto result = std::to_chars(buffer, buffer + sizeof(buffer), value);
if (result.ec == std::errc()) {
    *result.ptr = '\0'; // Добавляем нуль-терминатор, если нужно
    // buffer теперь содержит "12345"
}
Для чисел с плавающей точкой доступны дополнительные параметры форматирования:

C++
1
2
3
4
double pi = 3.14159;
auto result = std::to_chars(buffer, buffer + sizeof(buffer), pi, 
                           std::chars_format::fixed, 2);
// При успехе buffer будет содержать "3.14"
Красота симметрии между from_chars и to_chars проявляется при необходимости сериализации и десериализации данных. Они могут работать непосредственно с буферами без промежуточных преобразований, что делает их идеальными для высокопроизводительной обработки данных. Простой пример парсинга и генерации JSON-подобной структуры:

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
// Парсинг простой JSON-структуры
bool parse_point(const char* begin, const char* end, 
                double& x, double& y) {
    // Ищем открывающую скобку
    auto p = begin;
    while (p < end && *p != '{') ++p;
    if (p >= end || *p != '{') return false;
    ++p;
    
    // Ищем ключ "x"
    while (p < end && *p != '"') ++p;
    if (p+2 >= end || !(p[1] == 'x' && p[2] == '"')) return false;
    p += 3;
    
    // Ищем двоеточие и пробел
    while (p < end && *p != ':') ++p;
    if (p >= end) return false;
    ++p;
    while (p < end && std::isspace(*p)) ++p;
    
    // Читаем значение x
    auto res_x = std::from_chars(p, end, x);
    if (res_x.ec != std::errc()) return false;
    p = res_x.ptr;
    
    // Аналогично для y
    // ...
    
    return true;
}
 
// Генерация JSON-структуры
bool generate_point(char* begin, char* end, 
                   double x, double y) {
    if (begin + 20 >= end) return false; // Проверка на мин. размер буфера
    
    char* p = begin;
    *p++ = '{';
    *p++ = '"';
    *p++ = 'x';
    *p++ = '"';
    *p++ = ':';
    
    auto res_x = std::to_chars(p, end, x);
    if (res_x.ec != std::errc()) return false;
    p = res_x.ptr;
    
    *p++ = ',';
    *p++ = '"';
    *p++ = 'y';
    *p++ = '"';
    *p++ = ':';
    
    auto res_y = std::to_chars(p, end, y);
    if (res_y.ec != std::errc()) return false;
    p = res_y.ptr;
    
    *p++ = '}';
    *p++ = '\0';
    
    return true;
}
Уникальной особенностью пары from_chars/to_chars является их способность точно восстанавливать и воспроизводить числовые значения без потери точности. Если вы конвертируете число в строку с помощью to_chars, а затем обратно с помощью from_chars, в идеальных условиях вы получите точно то же значение, без потери данных из-за округлений или других преобразований. Эта гарантия особенно важна для систем, где необходимо хранить и восстанавливать числовые данные с высокой точностью, например, в финансовых приложениях или научных вычислениях.

Оба метода также хорошо работают с непрерывными потоками данных. Например, вы можете последовательно преобразовывать числа из строки в числа:

C++
1
2
3
4
5
6
7
8
9
10
11
const char* data = "123 456 789";
const char* end = data + strlen(data);
const char* p = data;
 
int values[3];
for (int i = 0; i < 3; ++i) {
    auto res = std::from_chars(p, end, values[i]);
    if (res.ec != std::errc()) break;
    p = res.ptr;
    while (p < end && std::isspace(*p)) ++p; // Пропускаем пробелы
}
Точно так же вы можете последовательно записывать числа в буфер:

C++
1
2
3
4
5
6
7
8
9
10
11
12
char buffer[100];
char* p = buffer;
char* end = buffer + sizeof(buffer);
 
int values[] = {123, 456, 789};
for (int value : values) {
    auto res = std::to_chars(p, end, value);
    if (res.ec != std::errc()) break;
    p = res.ptr;
    if (p < end) *p++ = ' ';
}
if (p < end) *(p-1) = '\0'; // Заменяем последний пробел на нуль-терминатор

Техническое погружение



Погрузимся глубже в технические аспекты std::from_chars. Эта функция, появившаяся в C++17, представлена наборами перегруженных вариантов для различных числовых типов. Давайте рассмотрим их сигнатуры и особенности использования. Для целочисленных типов функция имеет следующий вид:

C++
1
2
3
4
std::from_chars_result from_chars(const char* first, 
                                 const char* last, 
                                 IntegralType& value,
                                 int base = 10);
Здесь:
  • first — указатель на начало диапазона символов для преобразования.
  • last — указатель на конец диапазона (позиция после последнего символа).
  • value — переменная, куда будет записан результат преобразования.
  • base — основание системы счисления, может быть от 2 до 36.

IntegralType включает в себя все целочисленные типы языка: int, long, unsigned int, char и т.д.

Для чисел с плавающей точкой сигнатура немного отличается:

C++
1
2
3
4
std::from_chars_result from_chars(const char* first, 
                                 const char* last, 
                                 FloatingType& value,
                                 std::chars_format fmt = std::chars_format::general);
Где FloatingType — это float, double или long double, а fmt определяет формат представления числа.

Важной особенностью аргумента base для целых чисел является его гибкость. Поддержка базы от 2 до 36 позволяет работать с числами в различных системах счисления, включая двоичную, восьмеричную, десятичную и шестнадцатеричную. Символы, представляющие цифры выше 9, интерпретируются как латинские буквы от 'a' до 'z' (или 'A' до 'Z') — это стандартная конвенция для представления цифр в системах счисления с основанием больше 10.

C++
1
2
3
4
5
// Преобразование шестнадцатеричного числа
const std::string hex = "1A3F";
int value = 0;
auto result = std::from_chars(hex.data(), hex.data() + hex.size(), value, 16);
// При успехе value будет содержать 6719
Для чисел с плавающей точкой параметр fmt предоставляет контроль над форматом интерпретации. Возможные значения:
  • std::chars_format::scientific — требует экспоненциальной формы (например, "1.234e+5").
  • std::chars_format::fixed — требует десятичной точки, запрещает экспоненту (например, "123.45").
  • std::chars_format::hex — интерпретирует число в шестнадцатеричном представлении IEEE 754.
  • std::chars_format::general — разрешает как экспоненциальную форму, так и простое десятичное представление.

Эти режимы дают точный контроль над тем, какие форматы чисел считаются допустимыми, что полезно при разборе строго форматированных данных. Вот несколько примеров использования различных форматов:

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <charconv>
#include <string>
#include <iostream>
 
int main() {
    std::string inputs[] = {
        "1.01",         // fixed format
        "-67.90000",    // fixed format
        "20.9",         // not in scientific format
        "20.9e+0",      // scientific format
        "-20.9e+1",     // scientific format
        "F.F",          // hex format
        "-10.1"         // can be hex (-16.0625)
    };
    
    double value = 0;
    
    for (const auto& str : inputs) {
        // Пробуем научную нотацию
        auto res = std::from_chars(str.data(), str.data() + str.size(), 
                               value, std::chars_format::scientific);
        if (res.ec == std::errc())
            std::cout << str << " (scientific): " << value << '
';
            
        // Пробуем фиксированную нотацию
        res = std::from_chars(str.data(), str.data() + str.size(), 
                         value, std::chars_format::fixed);
        if (res.ec == std::errc())
            std::cout << str << " (fixed): " << value << '
';
            
        // Пробуем шестнадцатеричную нотацию
        res = std::from_chars(str.data(), str.data() + str.size(), 
                         value, std::chars_format::hex);
        if (res.ec == std::errc())
            std::cout << str << " (hex): " << value << '
';
    }
}
Такая гибкость делает std::from_chars мощным инструментом при разработке парсеров для различных форматов данных.

При работе с std::from_chars стоит учитывать некоторые тонкости:
1. Поведение при ошибке преобразования. Когда функция не может преобразовать строку (например, если строка содержит недопустимые символы), она устанавливает ec в std::errc::invalid_argument и возвращает ptr равным first. Это важный момент — по значению ptr можно определить, сколько символов успешно преобразовано.
2. Поведение при переполнении. Если число слишком велико или слишком мало для целевого типа, функция устанавливает ec в std::errc::result_out_of_range, но ptr при этом указывает на первый символ, не соответствующий шаблону. Это значит, что функция все равно выполнила парсинг числа, даже если оно не поместилось в целевой тип — информация о начале и длине числа в строке не теряется.
3. Префиксы систем счисления. В отличие от функций вроде strtol, from_chars не распознает автоматически префиксы систем счисления вроде "0x" для шестнадцатеричных чисел или "0" для восьмеричных. Система счисления задается явно через параметр base, а префикс, если он есть, должен обрабатываться отдельно.

Пример правильной обработки шестнадцатеричных чисел с префиксом "0x":

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
const std::string hex_str = "0x1A3F";
int value = 0;
 
// Проверяем наличие префикса и пропускаем его
const char* start = hex_str.data();
const char* end = start + hex_str.size();
if (hex_str.size() >= 2 && hex_str[0] == '0' && 
   (hex_str[1] == 'x' || hex_str[1] == 'X')) {
    start += 2;
}
 
auto result = std::from_chars(start, end, value, 16);
// Теперь value содержит 6719, если преобразование успешно
Поскольку std::from_chars предназначена для высокопроизводительных сценариев, она не выполняет никаких дополнительных проверок или преобразований. Все, что выходит за рамки простого преобразования строки в число, должно обрабатываться на уровне приложения.

Если вы часто работаете с конкретным форматом, имеет смысл создать обертку вокруг from_chars, которая будет заботиться о типичных задачах, таких как проверка префиксов, обработка ошибок или настройка других параметров:

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
template <typename T>
bool parse_int(const std::string& str, T& value, int base = 10) {
    const char* start = str.data();
    const char* end = start + str.size();
    
    // Автоматически обнаруживаем и пропускаем префиксы
    if (str.size() >= 2) {
        if (str[0] == '0') {
            if (str[1] == 'x' || str[1] == 'X') {
                start += 2;
                base = 16;
            } else if (str[1] == 'b' || str[1] == 'B') {
                start += 2;
                base = 2;
            } else if (base == 10) { // Если база явно не задана
                base = 8;  // Традиционно 0 означало восьмеричное число
            }
        }
    }
    
    auto result = std::from_chars(start, end, value, base);
    return (result.ec == std::errc() && result.ptr == end);
}
Такая обертка делает функцию более удобной для повседневного использования, сохраняя при этом преимущества from_chars в плане производительности.

Локаль-независимое поведение и его влияние на производительность



Одним из ключевых преимуществ std::from_chars, которое радикально отличает его от предшественников, является полная независимость от локали системы. Эта особенность не только упрощает использование функции, но и является одним из главных факторов её впечатляющей производительности. Что же такое локаль и почему зависимость от неё влияет на производительность? Локаль — это набор региональных настроек, влияющих на форматирование чисел, дат, времени и других данных. Например, в США десятичным разделителем является точка (3.14), тогда как во многих европейских странах используется запятая (3,14). При работе с традиционными функциями конвертации каждое преобразование требует проверки текущей локали.

C++
1
2
3
4
5
6
// Традиционный подход с учётом локали
std::stringstream ss("3,14");
double value;
std::locale loc("de_DE"); // Немецкая локаль с запятой как десятичным разделителем
ss.imbue(loc);
ss >> value; // value будет содержать 3.14
Каждый такой вызов приводит к нескольким действиям:
  • Проверке текущей локали.
  • Загрузке связанных с локалью настроек.
  • Применению соответствующих правил форматирования во время преобразования.

Всё это создаёт значительные накладные расходы. Кроме того, локаль-зависимое поведение может быть источником трудноуловимых багов, когда программа работает по-разному на разных системах или при разных настройках окружения.

std::from_chars полностью отказывается от учёта локали и всегда использует фиксированные правила:
  • Точка как десятичный разделитель.
  • Буква 'e' или 'E' как разделитель экспоненты.
  • Никаких группировочных разделителей (вроде запятых в числе 1,000,000).

Это упрощение даёт сразу несколько преимуществ:
1. Предсказуемое поведение независимо от системы. Функция работает одинаково на любой платформе, с любыми настройками локали.
2. Отсутствие накладных расходов на проверку локали. Нет необходимости загружать и применять правила форматирования.
3. Возможность агрессивной оптимизации парсера. Зная, что формат строго определён, компилятор может генерировать более эффективный код.
4. Более прямолинейный код в приложениях. Разработчикам не нужно заботиться о временном переключении локалей для корретного разбора данных.

Исследования производительности показывают, что локаль-независимая реализация может быть в 2-3 раза быстрее аналогичной функции с поддержкой локалей. Это особенно заметно в приложениях, обрабатывающих большие объёмы данных или требующих низких задержек. Конечно, есть и обратная сторона — если ваше приложение работает с вводом данных пользователем и должно учитывать локальные форматы (например, с запятой в качестве десятичного разделителя), вам придётся самостоятельно преобразовывать строки перед использованием from_chars:

C++
1
2
3
4
5
6
std::string user_input = "3,14"; // Ввод пользователя в европейском формате
std::replace(user_input.begin(), user_input.end(), ',', '.'); // Замена запятой на точку
double value;
auto result = std::from_chars(user_input.data(), 
                        user_input.data() + user_input.size(), 
                        value);
Однако в большинстве высокопроизводительных сценариев, таких как разбор файлов с данными, сетевых протоколов или машинно-генерируемого контента, формат чаще всего строго определён и не зависит от локали пользователя. В таких случаях локаль-независимое поведение from_chars становится не ограничением, а преимуществом.

Если взглянуть на типичный конвейер обработки данных в современных приложениях, можно увидеть, что большая часть преобразований происходит на границах системы (ввод/вывод), где производительность особенно критична. Именно здесь std::from_chars и std::to_chars могут дать наибольший прирост эффективности благодаря своему локаль-независимому поведению.

Примеры оптимизаций компиляторов при работе с from_chars



Современные компиляторы C++ уделяют особое внимание оптимизации стандартных библиотечных функций, и std::from_chars не является исключением. Благодаря своему минималистичному дизайну и чётким гарантиям поведения, эта функция предоставляет компиляторам широкие возможности для агрессивных оптимизаций. Одна из самых распространённых оптимизаций, применяемых к from_chars — инлайнинг. Поскольку функция имеет чётко определённое поведение без побочных эффектов, компиляторы часто встраивают её код непосредственно в место вызова, устраняя накладные расходы на вызов функции. В результате после компиляции вместо вызова функции генерируется специализированный код, работающий напрямую с данными.

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Исходный код
int value;
auto res = std::from_chars(str.data(), str.data() + str.size(), value);
 
// После оптимизации компилятор может превратить это в нечто похожее на:
int value = 0;
const char* ptr = str.data();
const char* end = ptr + str.size();
bool negative = false;
 
if (ptr < end && *ptr == '-') {
    negative = true;
    ptr++;
}
 
while (ptr < end && *ptr >= '0' && *ptr <= '9') {
    value = value * 10 + (*ptr - '0');
    ptr++;
}
 
if (negative) value = -value;
Такая оптимизация особенно эффективна для небольших строк, где накладные расходы на вызов функции могли бы составить значительную часть общего времени выполнения.

Другая оптимизация — это векторизация. Современные процессоры поддерживают SIMD-инструкции (Single Instruction Multiple Data), позволяющие обрабатывать несколько элементов данных одной инструкцией. Компиляторы GCC и Clang способны использовать этот потенциал для ускорения преобразования чисел, особенно когда требуется обработать большой массив строк. Например, при конвертации массива целочисленных строк, компилятор может сгенерировать код, который одновременно проверяет несколько символов на принадлежность к диапазону цифр (от '0' до '9'). Это особенно эффективно для 64-битных архитектур с поддержкой AVX и AVX2.

Для целочисленных преобразований компиляторы часто применяют технику, называемую "умножение с накоплением" (multiply-accumulate). Вместо последовательного умножения и сложения, которые требуют двух операций, генерируется одна специальная инструкция, выполняющая обе операции атомарно. Это снижает число инструкций и повышает производительность.

В случае с плавающей точкой компиляторы могут оптимизировать разбор экспоненциального представления, раскручивая циклы и предвычисляя значения степеней 10 для наиболее распространенных показателей экспонент.

Примечательно, что в MSVC для архитектуры x86-64 реализация from_chars для целых чисел использует специальные ассемблерные вставки, задействующие инструкции SSE4.1 и BMI2, если доступны. Эти инструкции позволяют эффективно манипулировать битами и ускоряют обработку строк. Компилятор GCC начиная с версии 8 реализует специальную оптимизацию для from_chars на платформе ARM с NEON-инструкциями, что даёт прирост производительности до 40% на некоторых моделях смартфонов и встраиваемых систем. Интересная оптимизация применяется в Clang для шестнадцатеричных чисел. При определении базы 16 компилятор генерирует таблицу соответствия символов их числовым значениям и использует её для прямых преобразований, избегая условных ветвлений, которые могли бы вызвать промахи в предсказании переходов.

Учитывая эти оптимизации, неудивительно, что std::from_chars демонстрирует такую впечатляющую производительность по сравнению с традиционными методами. При разработке высокопроизводительных систем стоит учитывать не только чистую скорость алгоритма, но и то, насколько хорошо компиляторы могут его оптимизировать — и from_chars предоставляет идеальные условия для таких оптимизаций.

Развитие функционала в C++20 и C++23



Эволюция функций конвертации строк не остановилась после их введения в C++17. Стандарты C++20 и C++23 принесли несколько значительных улучшений, которые сделали std::from_chars ещё более мощным и универсальным инструментом.

В C++20 комитет по стандартизации сосредоточился на исправлении ошибок и улучшении совместимости между реализациями. Хотя явных новых функций для from_chars не было добавлено, стандарт уточнил детали реализации, что позволило компиляторам лучше оптимизировать эти функции и обеспечить более последовательное поведение на разных платформах.

C++23 принёс по-настоящему революционное нововведение — поддержку constexpr для целочисленных версий std::from_chars и std::to_chars. Эта возможность, предложенная в документе P2291R3, позволила использовать функции преобразования строк во время компиляции, что открыло совершенно новые сценарии применения.

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <charconv>
#include <optional>
#include <string_view>
 
constexpr std::optional<int> to_int(std::string_view sv) {
    int value {};
    const auto ret = std::from_chars(sv.begin(), sv.end(), value);
    if (ret.ec == std::errc{})
        return value;
    
    return {};
}
 
int main() {
    static_assert(to_int("hello") == std::nullopt);
    static_assert(to_int("10") == 10);
}
Этот код не только выполняется во время работы программы, но и проверяется во время компиляции благодаря static_assert. Компилятор вычисляет результат преобразования строк "hello" и "10" и проверяет утверждения, что первое преобразование возвращает std::nullopt (ошибка), а второе — значение 10. Такая возможность особенно полезна при разработке библиотек метапрограммирования и кода, требующего высокой эффективности. Теперь вы можете выполнять преобразования строк в числа на этапе компиляции без использования шаблонных метапрограммных трюков или макросов препроцессора.

Совместимость constexpr std::from_chars с другими компонентами C++, поддерживающими вычисления во время компиляции, такими как std::optional и std::string_view, создаёт богатую экосистему для разработки высокоэффективного кода.

На момент написания статьи эта возможность уже реализована в GCC 13, Clang 16 и MSVC 19.34, что делает её доступной большинству разработчиков на C++.

Ещё одно важное улучшение в C++23 касается унификации обработки ошибок. Специфика работы функций в "граничных" случаях была уточнена, что обеспечивает более надёжное и предсказуемое поведение между различными реализациями компиляторов. Например, теперь чётко определено поведение функции при обработке пустой строки или строки, состоящей только из знака минус:

C++
1
2
3
4
5
6
7
8
int value = 0;
std::string empty = "";
auto res1 = std::from_chars(empty.data(), empty.data() + empty.size(), value);
// Гарантированно вернёт invalid_argument на всех платформах
 
std::string minus = "-";
auto res2 = std::from_chars(minus.data(), minus.data() + minus.size(), value);
// Также гарантированно вернёт invalid_argument
Такая стандартизация поведения в краевых случаях особенно важна для разработки переносимого кода, который должен одинаково работать на разных платформах. Стоит отметить, что в то время как целочисленные перегрузки получили поддержку constexpr в C++23, версии для чисел с плавающей точкой пока что не поддерживают вычисления во время компиляции. Это связано со сложностью обеспечения точного соответствия между вычислениями во время компиляции и во время выполнения для операций с плавающей точкой. Однако это ограничение, вероятно, будет снято в будущих стандартах C++. На момент публикации статьи уже велась работа над предложением по добавлению constexpr поддержки для плавающих типов в std::from_chars в C++26.

Ещё одно интересное направление развития — улучшенная поддержка пользовательских типов. Хотя стандартная библиотека по-прежнему предоставляет только перегрузки для встроенных числовых типов, разработчики компиляторов начали экспериментировать с расширениями, позволяющими использовать from_chars с пользовательскими типами, реализующими определённые интерфейсы.

В C++26, по предварительным данным, нас ждёт новое интересное дополнение — специальный метод bool operator() для структуры from_chars_result, предложенный в P2497R0 и уже включённый в рабочий черновик стандарта. Этот оператор будет возвращать ec == std::errc{}, что позволит писать более компактный код:

C++
1
2
3
4
5
6
7
8
9
// Вместо этого
if (res.ec == std::errc()) {
    // Использовать результат
}
 
// Можно будет писать так
if (res) {
    // Использовать результат
}
Такой синтаксический сахар сделает код чище и читабельнее, особенно в ситуациях, когда from_chars используется в цепочке операций. Поддержка этой возможности уже реализована в GCC 14 и Clang 18.

Эволюция std::from_chars от C++17 до C++23 и дальше показывает приверженность комитета по стандартизации идее создания высокопроизводительных, надёжных и удобных в использовании инструментов для базовых операций. Постепенно функция, изначально задуманная как низкоуровневый примитив, становится полноценным компонентом современной библиотеки C++, сочетающим эффективность с удобством использования.

Поддержка плавающей точки в C++17 vs C++23: детальное сравнение



Одним из наиболее значимых аспектов эволюции функций std::from_chars является изменение поддержки чисел с плавающей точкой между C++17 и C++23. Когда стандарт C++17 впервые представил эти функции, реализация для плавающей точки была опциональной и зачастую отсутствовала в ранних версиях компиляторов.

В C++17 спецификация для работы с плавающей точкой была определена, но оставляла многие детали на усмотрение разработчиков компиляторов. Это привело к неоднородной поддержке между разными платформами. Например, GCC долгое время предоставлял только функциональность для целых чисел, в то время как Microsoft Visual C++ относительно рано добавил поддержку плавающей точки. Основные проблемы реализации from_chars для плавающей точки в C++17:

C++
1
2
3
// C++17: Базовая форма для плавающей точки
auto result = std::from_chars(str.data(), str.data() + str.size(), 
                        value, std::chars_format::general);
1. Точность округления. В C++17 не было строгих гарантий "правильного округления" для преобразований с плавающей точкой.
2. Обработка граничных случаев. Некоторые компиляторы по-разному интерпретировали обработку таких строк как "inf", "nan" или чисел на границе типов.
3. Поддержка экзотических форматов. Обработка шестнадцатеричных чисел с плавающей точкой могла отличаться между реализациями.

C++23 значительно усовершенствовал эту картину. Хотя базовый синтаксис функций остался прежним, были внесены важные уточнения и улучшения:

C++
1
2
3
// C++23: Синтаксически то же самое, но с улучшенной семантикой
auto result = std::from_chars(str.data(), str.data() + str.size(), 
                        value, std::chars_format::general);
Ключевые улучшения включают:
1. Стандартизированная точность. C++23 определяет более строгие требования к алгоритмам преобразования, гарантируя, что результат будет наиболее близким представимым числом для данного типа (так называемое "правильное округление").
2. Унифицированная обработка специальных значений. Теперь все реализации должны одинаково обрабатывать строковые представления бесконечности и NaN.
3. Расширенная поддержка шестнадцатеричных чисел. Формат std::chars_format::hex получил более детальную спецификацию, устраняющую неоднозначности в предыдущем стандарте.
4. Улучшенная диагностика ошибок. Пользователи получают более точную и согласованную информацию при ошибках преобразования.

Простой пример демонстрирует различия в точности:

C++
1
2
3
4
5
6
7
// Вот строка, которая находится между двумя представимыми числами для float
const char* str = "0.1000000000000000055511151231257827";
float f;
 
// В C++17 различные реализации могли дать разные результаты
// В C++23 гарантировано округление до ближайшего представимого значения
auto res = std::from_chars(str, str + strlen(str), f);
Для hex-представлений плавающих чисел различия между стандартами еще заметнее:

C++
1
2
3
4
5
6
7
8
9
// Шестнадцатеричная запись числа Pi
const char* hex_pi = "0x1.921fb54442d18p+1";
double pi_value;
 
// В C++17 некоторые компиляторы могли не поддерживать эту форму
// или интерпретировать различные части по-разному
// В C++23 поведение строго определено для всех реализаций
auto res = std::from_chars(hex_pi, hex_pi + strlen(hex_pi), 
                      pi_value, std::chars_format::hex);
Ещё один значимый аспект — обработка подстановки. В C++23 строго определено, как функция должна обрабатывать строки, которые могут представлять более чем одно число (например, "123ABC"). Функция всегда остановится на первом символе, не соответствующем числовому шаблону, и вернет указатель на него.

Стандарт C++23 также внёс ясность в обработку чисел, выходящих за пределы диапазона типа, например, очень маленьких чисел, близких к подпининовым (subnormal). Теперь все реализации должны точно определять, когда возвращать ошибку std::errc::result_out_of_range, а когда округлять до нуля или минимального нормализованного значения. Эти улучшения делают std::from_chars более надёжным и предсказуемым инструментом для работы с числами с плавающей точкой, особенно в контексте разработки переносимого кода, который должен одинаково работать на разных платформах и компиляторах.

Сценарии применения



При внедрении from_chars в свои проекты, следуйте этим рекомендациям:

1. Используйте буферы правильно: Поскольку функция требует указателей на начало и конец буфера, важно убедиться, что эти указатели действительно определяют корректный диапазон памяти.

C++
1
2
3
4
5
6
7
8
9
// Обработка ошибки переполнения буфера
const char* buffer = "12345";
size_t length = strlen(buffer);
int value;
auto result = std::from_chars(buffer, buffer + length, value);
// Проверяем, не вышли ли за пределы буфера
if (result.ptr > buffer + length) {
    // Что-то пошло не так
}
2. Кэшируйте результаты для повторяющихся значений: Если вы преобразуете одни и те же строки многократно, рассмотрите возможность кэширования результатов.

3. Комбинируйте с string_view: Использование std::string_view вместе с from_chars позволяет избежать копирования строк:

C++
1
2
3
4
bool parse_number(std::string_view sv, int& out_value) {
    auto result = std::from_chars(sv.data(), sv.data() + sv.size(), out_value);
    return result.ec == std::errc();
}
4. Создавайте удобные обёртки: API может показаться неуклюжим для повседневного использования. Создайте свои обёртки, добавляющие удобства без потери производительности:

C++
1
2
3
4
5
6
7
8
template <typename T>
std::optional<T> try_parse_int(std::string_view sv) {
    T value;
    auto result = std::from_chars(sv.data(), sv.data() + sv.size(), value);
    if (result.ec == std::errc())
        return value;
    return std::nullopt;
}
5. Внимательно проверяйте результаты: from_chars не выбрасывает исключений, поэтому непроверенные ошибки могут привести к неопределённому поведению:

C++
1
2
3
4
5
6
7
double value;
auto result = std::from_chars(str.data(), str.data() + str.size(), value);
if (result.ec == std::errc()) {
    // Используйте value только если преобразование успешно
} else {
    // Обязательно обработайте ошибку!
}
6. Для плавающей точки выбирайте правильный формат: Если вы знаете формат входных данных, указывайте соответствующий std::chars_format вместо general, это может ускорить преобразование:

C++
1
2
3
// Если известно, что число в научной нотации
double value;
auto result = std::from_chars(str.data(), str.data() + str.size(), value, std::chars_format::scientific);
7. Используйте constexpr версии для конфигурационных значений: В C++23 вы можете преобразовывать строковые константы в числа во время компиляции, что особенно полезно для конфигурационных значений и compile-time вычислений.

При выборе между from_chars и другими методами конвертации, учитывайте следующие факторы:
  • Простые единичные преобразования: Если производительность некритична и вам нужно преобразовать одну строку, std::stoi или даже std::stringstream могут быть более удобными.
  • Локаль-зависимое поведение: Если требуется учитывать региональные настройки пользователя, from_chars придётся дополнять собственным кодом для адаптации ввода.
  • Ограниченные ресурсы: На встраиваемых системах с ограниченной памятью from_chars может быть предпочтительнее, так как не требует динамических выделений памяти.

Паттерны замены устаревшего кода конвертации на современные альтернативы



При модернизации устаревшего кода важно не просто механически заменять старые функции на новые, а делать это систематически, придерживаясь определённых паттернов. Рассмотрим наиболее распространённые сценарии замены старых методов преобразования строк на современные альтернативы с использованием std::from_chars.

Замена atoi и родственных функций



Старый код с использованием atoi обычно не предусматривает никакой обработки ошибок:

C++
1
2
int value = atoi(str.c_str());
// Использование value без проверки успешности преобразования
Современный вариант с from_chars и обработкой ошибок:

C++
1
2
3
4
5
6
7
int value = 0;
auto result = std::from_chars(str.data(), str.data() + str.size(), value);
if (result.ec == std::errc()) {
    // Использовать value
} else {
    // Обработать ошибку
}

Замена strtol с проверкой ошибок



Исходный код со strtol:

C++
1
2
3
4
5
6
7
8
char* end;
errno = 0;
long value = strtol(str.c_str(), &end, 10);
if (errno == ERANGE) {
    // Обработка переполнения
} else if (*end != '\0') {
    // Обработка неполного преобразования
}
Модернизированный вариант:

C++
1
2
3
4
5
6
7
8
9
long value = 0;
auto result = std::from_chars(str.data(), str.data() + str.size(), value);
if (result.ec == std::errc::result_out_of_range) {
    // Обработка переполнения
} else if (result.ec == std::errc::invalid_argument) {
    // Обработка недопустимого ввода
} else if (result.ptr != str.data() + str.size()) {
    // Обработка неполного преобразования
}

Замена std::stringstream



Старый подход с stringstream:

C++
1
2
3
4
5
6
7
std::stringstream ss(str);
double x, y;
if (ss >> x >> y) {
    // Использовать x и y
} else {
    // Ошибка преобразования
}
Замена на from_chars для повышения производительности:

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
double x = 0.0, y = 0.0;
const char* p = str.data();
const char* end = p + str.size();
 
auto res_x = std::from_chars(p, end, x);
if (res_x.ec != std::errc()) {
    // Обработка ошибки для x
    return;
}
 
// Пропуск пробелов
p = res_x.ptr;
while (p < end && std::isspace(*p)) ++p;
 
auto res_y = std::from_chars(p, end, y);
if (res_y.ec != std::errc()) {
    // Обработка ошибки для y
    return;
}
 
// Использовать x и y

Замена std::stoi и аналогов



Старый код с исключениями:

C++
1
2
3
4
5
6
7
8
try {
    int value = std::stoi(str);
    // Использовать value
} catch (const std::invalid_argument&) {
    // Обработка неверного формата
} catch (const std::out_of_range&) {
    // Обработка переполнения
}
Новый код без исключений:

C++
1
2
3
4
5
6
7
8
9
int value = 0;
auto result = std::from_chars(str.data(), str.data() + str.size(), value);
if (result.ec == std::errc()) {
    // Использовать value
} else if (result.ec == std::errc::invalid_argument) {
    // Обработка неверного формата
} else if (result.ec == std::errc::result_out_of_range) {
    // Обработка переполнения
}
Последовательно применяя эти паттерны, можно значительно повысить производительность кода и улучшить его надёжность, сохранив при этом исходную логику обработки ошибок.

Перевод строк std::string, std::wstring в Unicode (String)
Собственно столкнулся с проблемой, как корректно перевести к примеру текст из Edit1-&gt;Text в std::string или std::wstring и соответственно обратно?...

преобразование *this в std::shared_ptr
Доброго времени суток уважаемые форумчане. у меня назрел такой вопрос, пишу программу в которой используется паттерн визитор и хотелось бы...

Преобразование std::string в char*
Несомненно, работать с определенным в STL классом string работать в разы приятнее, чем с обычным char*. Однако иногда все же встает вопрос...

std::list - преобразование типов в контейнере
Здравствуйте. Суть проблемы попробую передать в коде class A { }; class B : public A { public: B (int) {/*...*/} B...

Не воспринимает ни std::cout, ни std::cin. Вобщем ничего из std. Также не понимает iostream
Здравствуйте! Я хотел начать изучать язык C++. Набрал литературы. Установил Microsoft Visual C++ 2005 Express Edition. Образ диска скачал с сайта...

Поиск в std::vector < std::pair<UInt32, std::string> >
Подскажите пожалуйста, как осуществить поиск элемента в std::vector &lt; std::pair&lt;UInt32, std::string&gt; &gt; по ключу, а также по...

ошибка error: cannot convert 'std::string {aka std::basic_string<char>}' to 'std::string* {aka std::basic_stri
на вод поступают 2 строки типа string. определить количество вхождений строки 2 в строку 1 ошибка error: cannot convert 'std::string {aka...

STL std::set, std::pair, std::make_pair
Я не знаю как описать тему в двух словах, поэтому не обращайте внимание на название темы. Собственно перейдем к нашим баранам: есть...

На основе исходного std::vector<std::string> содержащего числа, создать std::vector<int> с этими же числами
подскажите есть вот такая задача. Есть список . Создать второй список, в котором будут все эти же числа, но не в виде строк, а в виде int...

Std + удаление пустых строк
Доброго вечера.Ребята подскажите как удалить пустые строки из std::string s; То как то не выходит

Наборы строк std::string
Наборы строк std::string. В качестве набора можно использовать std::vector или другой контейнер. Код решения должен содержать проверку...

Cравнение строк std::string
например есть две переменные типа &quot;string&quot;: string a = &quot;HelloWorld&quot;; string b = &quot;HelloAll&quot;; Мне нужно например знать, совпадают ли первые 5...

Размещено в Без категории
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
Всего комментариев 3
Комментарии
  1. Старый комментарий
    C++
    1
    
    auto res = std::from_chars(str.data(), str.data() + str.size(), value, fmt);
    Почему cpp не мог быть таким, что мешало?
    C++
    1
    
    Auto res = from_Chars(str.Data, str.Size, value, fmt)
    Запись от testuser2 размещена 16.03.2025 в 03:39 testuser2 на форуме
  2. Старый комментарий
    Аватар для NullReferenced
    Цитата Сообщение от testuser2
    Почему cpp не мог быть таким, что мешало?
    Отсутствие "сишности" в этом коде. Он выглядит как джава или шарп.
    Каждый язык - заложник своей идеалогии.
    Запись от NullReferenced размещена 16.03.2025 в 11:54 NullReferenced вне форума
  3. Старый комментарий
    Аватар для XLAT
    NullReferenced,
    весьма достойно.
    Запись от XLAT размещена 19.03.2025 в 02:05 XLAT вне форума
 
Новые блоги и статьи
Обнаружение объектов в реальном времени на Python с YOLO и OpenCV
AI_Generated 29.04.2025
Компьютерное зрение — одна из самых динамично развивающихся областей искусственного интеллекта. В нашем мире, где визуальная информация стала доминирующим способом коммуникации, способность машин. . .
Эффективные парсеры и токенизаторы строк на C#
UnmanagedCoder 29.04.2025
Обработка текстовых данных — частая задача в программировании, с которой сталкивается почти каждый разработчик. Парсеры и токенизаторы составляют основу множества современных приложений: от. . .
C++ в XXI веке - Эволюция языка и взгляд Бьярне Страуструпа
bytestream 29.04.2025
C++ существует уже более 45 лет с момента его первоначальной концепции. Как и было задумано, он эволюционировал, отвечая на новые вызовы, но многие разработчики продолжают использовать C++ так, будто. . .
Слабые указатели в Go: управление памятью и предотвращение утечек ресурсов
golander 29.04.2025
Управление памятью — один из краеугольных камней разработки высоконагруженных приложений. Го (Go) занимает уникальную нишу в этом вопросе, предоставляя разработчикам автоматическое управление памятью. . .
Разработка кастомных расширений для компилятора C++
NullReferenced 29.04.2025
Создание кастомных расширений для компиляторов C++ — инструмент оптимизации кода, внедрения новых языковых функций и автоматизации задач. Многие разработчики недооценивают гибкость современных. . .
Гайд по обработке исключений в C#
stackOverflow 29.04.2025
Разработка надёжного программного обеспечения невозможна без грамотной обработки исключительных ситуаций. Любая программа, независимо от её размера и сложности, может столкнуться с непредвиденными. . .
Создаем RESTful API с Laravel
Jason-Webb 28.04.2025
REST (Representational State Transfer) — это архитектурный стиль, который определяет набор принципов для создания веб-сервисов. Этот подход к построению API стал стандартом де-факто в современной. . .
Дженерики в C# - продвинутые техники
stackOverflow 28.04.2025
История дженериков началась с простой идеи — создать механизм для разработки типобезопасного кода без потери производительности. До их появления программисты использовали неуклюжие преобразования. . .
Тестирование в Python: PyTest, Mock и лучшие практики TDD
py-thonny 28.04.2025
Тестирование кода играет весомую роль в жизненном цикле разработки программного обеспечения. Для разработчиков Python существует богатый выбор инструментов, позволяющих создавать надёжные и. . .
Работа с PDF в Java с iText
Javaican 28.04.2025
Среди всех форматов PDF (Portable Document Format) заслуженно занимает особое место. Этот формат, созданный компанией Adobe, превратился в универсальный стандарт для обмена документами, не зависящий. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2025, CyberForum.ru