如何将std::string_view转换为double?

24

我正在为一个应用程序编写一个自定义选项文件的C++解析器。我有一个循环,从文本文件中读取形如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的确切部分的那个点,但我无法弄清如何从std :: string_view 读取一个double

我知道std::stod,但它不适用于std::string_view。它让我写

double value = std::stod(std::string(value_str));
然而,这种方法很丑陋,因为它转换成一个实际上并不需要的字符串,尽管在我的情况下可能不会产生明显的影响,但如果需要从文本文件中读取大量数字,则可能太慢了。
另一方面,atof 是行不通的,因为我不能保证空终止符。我可以通过在构造useful_statement时添加\0来进行修改,但这会使代码对读者来说难以理解,并且使其在修改/重构代码时容易出错。
那么,有什么干净、直观且相当高效的方法可以做到这一点呢?

1
你是否愿意使用boost?我认为你可以使用boost::convert<double>(stringview);来完成这个任务。我在这里找到了它...页面上的最后一条评论 https://github.com/boostorg/convert/issues/29 - Millie Smith
1
不错的发现。可能是 boost::convert<double>(stringview, stringview.length()),这样做肯定比转换为字符串更简洁,而且希望速度更快。唯一的缺点是需要额外依赖于 boost。 - patatahooligan
1
离题:显然这是伪代码,但在实现“while(not EOF)”时要小心。简单的while (!stream.eof())有一些麻烦的问题。 - user4581301
通常情况下,应该使用类似 while ( stream << statement ) 的语句。 - Arne Vogel
请不要评论有关从流中读取部分的内容。我特意使用伪代码编写它,以保持讨论的重点。 - patatahooligan
@MillieSmith @patatahooligan boost::convert 的修复支持 std::string_view 是将范围复制到数组并添加 NUL 终止符:https://github.com/boostorg/convert/commit/ab1a43676e04a7c73602e6d1cb2337ea5402c4df - Andreas Magnusson
2个回答

25

因为你将问题标记为C++1z,这 (理论上) 意味着你可以使用from_chars。它可以处理你的字符串转数字转换,而不需要更多东西,只需要一对const char*

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

当然,这需要您的标准库提供from_chars的实现。


3
from_chars()需要传入一个double&而不是一个double*。另外,这个接口很别扭...鉴于没有人提供它,也许可以考虑将其改为使用string_view - Barry
4
我建议更改调用的内容,但这仍然无效。"back" 是最后一个字符,它需要是一个指向最后一个字符之后的指针。 - Nicol Bolas
4
@NicolBolas,您甚至不需要迭代器,只需使用.data().data() + .size()就像您基本上正在做的那样。 - Barry
3
把两个参数用来指代同一件事是值得质疑的,把它称为“普遍”的就更加糟糕了。 - Barry
7
double版本甚至在gcc-10中也没有被实现,就像另一个<regex>一样的惨案。 - Maxim Egorushkin
显示剩余11条评论

3

标题头:

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

测试编译器:

  • MSVC 2017

p.s. 可以使用 vcpkg 轻松安装 Boost(默认为32位,第二个命令用于64位):

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

更新:显然,许多Boost函数在内部使用字符串流,这会锁定全局OS区域设置。因此它们具有可怕的多线程性能。我现在建议使用类似于substr的stoi()。请参见:Safely convert std::string_view to int (like stoi or atoi) Boost的这种奇怪习惯使得大多数Boost字符串处理在多线程环境下几乎没有用处,这是一个奇怪的悖论。这是一种艰苦获得的经验 - 如果您有任何疑问,请自行衡量。48核机器与2核机器相比,在许多Boost调用方面运行速度不变。因此,我现在像传染瘟疫一样避免使用Boost的某些部分,因为任何东西都可能依赖于该死的全局OS区域设置锁。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接