Как преобразовать std::string_view в double?

Я пишу анализатор С++ для пользовательского файла параметров для приложения. У меня есть цикл, который читает строки в виде option=value из текстового файла, где value нужно преобразовать в double. В псевдокоде он делает следующее:

while(not EOF)
    statement <- read_from_file
    useful_statement <- remove whitespaces, comments, etc from statement
    equal_position <- find '=' in useful_statement
    option_str <- useful_statement[0:equal_position)
    value_str <- useful_statement[equal_position:end)
    find_option(option_str) <- double(value_str)

Чтобы справиться с разделением строк и передачей их функциям, я использую std::string_view, потому что это позволяет избежать чрезмерного копирования и четко указывает цель просмотра сегментов ранее существовавшего std::string. Я сделал все до такой степени, что std::string_view value_str указывает именно на ту часть useful_statement, которая содержит значение, которое я хочу извлечь, но я не могу понять, как прочитать double из std::string_view.

Я знаю std::stod, который не работает с std::string_view. Это позволяет мне писать

double value = std::stod(std::string(value_str));

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

С другой стороны, atof не будет работать, потому что я не могу гарантировать нулевой терминатор. Я мог бы взломать его, добавив \0 к useful_statement при его построении, но это сделает код запутанным для читателя и сделает его слишком легким для взлома, если код будет изменен/рефакторинг.

Итак, что было бы чистым, интуитивно понятным и достаточно эффективным способом сделать это?


person patatahooligan    schedule 11.08.2017    source источник
comment
Вы согласны с использованием boost? Я думаю, вы можете сделать это с помощью boost::convert<double>(stringview);. Я взял это отсюда... последний комментарий на странице github.com/boostorg/convert/ вопросы/29   -  person Millie Smith    schedule 11.08.2017
comment
Хорошая находка. Хотя, наверное, boost::convert<double>(stringview, stringview.length()). Это, безусловно, чище, чем преобразование в строку, и, надеюсь, быстрее. Единственным недостатком является дополнительная зависимость от повышения.   -  person patatahooligan    schedule 11.08.2017
comment
Не по теме: очевидно, что это псевдокод, но позаботьтесь о том, как вы реализуете while (не EOF). Тривиальный while (!stream.eof()) имеет несколько неприятных ошибок.   -  person user4581301    schedule 11.08.2017
comment
user4581301 Обычно вместо этого следует использовать что-то вроде while ( stream << statement )  -  person Arne Vogel    schedule 14.08.2017
comment
Пожалуйста, не комментируйте чтение из потока. Я специально написал это в псевдокоде, чтобы поддерживать дискуссию по делу.   -  person patatahooligan    schedule 14.08.2017
comment
@MillieSmith @patatahooligan Исправление в boost::convert для поддержки std::string_view состоит в том, чтобы скопировать диапазон в массив и завершить NUL: ">github.com/boostorg/convert/commit/   -  person Andreas Magnusson    schedule 08.01.2021


Ответы (2)


Поскольку вы отметили свой вопрос с помощью C++1z, это (теоретически) означает, что у вас есть доступ к from_chars. Он может обрабатывать ваше преобразование строки в число, не требуя ничего, кроме пары const char*:

double dbl;
auto result = from_chars(value_str.data(), value_str.data() + value_str.size(), dbl);

Конечно, для этого требуется, чтобы ваша стандартная библиотека обеспечивала реализацию from_chars.

person Nicol Bolas    schedule 11.08.2017
comment
from_chars() получает double&, а не double*. Кроме того, это такой неуклюжий интерфейс... учитывая, что его никто не предоставляет, может быть, это хорошая возможность, чтобы он занял string_view... - person Barry; 11.08.2017
comment
Это было бы именно то, что я ищу, если бы это было реализовано. Кстати, я рекомендую изменить вызов на from_chars(&value_str.front(), &value_str.back(), dbl) для удобства чтения. @Barry: нет особых причин заставлять его принимать string_view Работа с char* делает его универсальным, и получить его от string_view тривиально. - person patatahooligan; 11.08.2017
comment
@Barry: Было бы менее неловко, если бы basic_string_view гарантировал, что его итераторы были указателями. - person Nicol Bolas; 11.08.2017
comment
@patatahooligan: Я рекомендую изменить вызов на Это все еще не работает. back — это последний символ. Это должен быть указатель после последнего символа. - person Nicol Bolas; 11.08.2017
comment
Исправлено несколько опечаток. Обратите внимание, что он возвращает ссылку, а не значение. - person patatahooligan; 11.08.2017
comment
@patatahooligan: &back() не является указателем после последнего символа; это указатель на последний символ. Ваш код отрезал бы последний символ. - person Nicol Bolas; 11.08.2017
comment
@NicolBolas Вам даже не нужны итераторы, вы можете просто использовать .data() и .data() + .size() точно так же, как вы это делаете. - person Barry; 11.08.2017
comment
@patatahooligan Использование двух аргументов для ссылки на одну вещь сомнительно, называть ее универсальной еще хуже. - person Barry; 11.08.2017
comment
@patatahooligan Это не гарантирует работу; string_view::iterator может не быть указателем. Если это не так, не имеет значения, используете ли вы back или end, как указывают другие, потому что он не будет компилироваться. - person Daniel H; 11.08.2017
comment
Извините, я не заметил, что он использует последовательность [first,last), а не [first,last]. Это как-то раздражает... - person patatahooligan; 11.08.2017
comment
@DanielH Согласно cppreference.com, это не итератор. Проблема в том, что заметил Никол Болас. @ Барри, это универсально, потому что оно работает с чем угодно, например, char[], string и string_view, и даже с необработанной памятью, если вы действительно этого хотите. - person patatahooligan; 11.08.2017
comment
Я действительно считаю, что использование Барри .data() является самым чистым и наиболее интуитивно понятным выражением для использования. - person patatahooligan; 11.08.2017
comment
@patatahooligan Ой, извините, я пропустил это. Что касается комментария Барри, все они могут быть преобразованы в string_view по крайней мере так же легко, как они могут быть преобразованы в char*. Тем не менее, для симметрии с to_chars, я думаю, они приняли правильное решение, сделав это подписью (хотя я не возражал бы против перегрузки string_view). - person Daniel H; 11.08.2017
comment
Я только что заметил, что в соответствии со стандартом использование operator[](size_type pos) с pos >= size() является неопределенным поведением, означающим, что реализация не требуется для оценки &value_str[0] чего-то значимого для пустых string_view. С другой стороны, data() всегда требуется для возврата указателя, так что диапазон [data(); data() + size()) является допустимым, а from_chars требует, чтобы диапазон [first, last) был допустимым. Измените свой ответ, чтобы использовать data() и data()+size() для учета пустого случая string_view. - person patatahooligan; 23.08.2017
comment
Я не могу найти его на странице состояния, на которую ссылается этот ответ, но я могу подтвердить, что libstdc++, поставляемый с g++ 8.1, предоставляет from_chars. - person Jeffrey Bosboom; 12.05.2018
comment
Версия @JeffreyBosboom double не реализована даже в gcc-10. Очередное <regex> подобное фиаско. - person Maxim Egorushkin; 11.03.2020

Заголовки:

#include <boost/convert.hpp>
#include <boost/convert/strtol.hpp>

Затем:

std::string x { "aa123.4"};
const std::string_view y(x.c_str()+2, 5); // Window that views the characters "123.4".

auto value = boost::convert<double>(y, boost::cnv::strtol());
if (value.has_value())
{
    cout << value.get() << "\n"; // Prints: 123.4
}

Проверенные компиляторы:

  • МСВК 2017

p.s. Можно легко установить Boost с помощью vcpkg (по умолчанию 32-битная, вторая команда для 64-битной):

vcpkg install boost-convert
vcpkg install boost-convert:x64-windows

Обновление: по-видимому, многие функции Boost используют внутри строковые потоки, которые блокируют глобальную локаль ОС. Поэтому у них ужасная многопоточная производительность**.

Вместо этого я бы рекомендовал что-то вроде stoi() с substr. См.: Безопасное преобразование std::string_view в int (например, стои или атои)

** Эта странная особенность Boost делает большую часть обработки строк Boost абсолютно бесполезной в многопоточной среде, что действительно является странным парадоксом. Это голос с трудом добытого опыта - измерьте его сами, если у вас есть какие-то сомнения. 48-ядерная машина работает не быстрее со многими вызовами Boost по сравнению с 2-ядерной машиной. Так что теперь я избегаю определенных частей Boost, как пресловутой чумы, поскольку что угодно может зависеть от этой проклятой глобальной блокировки локали ОС.

person Contango    schedule 16.09.2019