在 C++ 中将模板友好字符串转换为数字

Template friendly string to numeric in C++

在 C++ 标准库中有将字符串类型转换为数字类型的函数:

stoi
stol
stoll
stoul
stoull
stof
stod
stold

但我发现在模板代码中使用它们很乏味。为什么没有像这样的模板函数:

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

将字符串转换为数字类型?

我看不出有任何技术原因不使用它们,但也许我遗漏了什么。它们可以专门用于调用底层命名函数并使用 enable_if/concepts 禁用非数字类型。

标准库中是否有任何模板友好的替代方法可以有效地将字符串转换为数字类型或反过来?

Why there are no template functions something like:

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 != std::errc{}) {
        // error with the conversion
    } else {
        // conversion successful, do stuff with value
    }
}

如您所见,它可以在通用上下文中工作。

它不是模板,它不适用于语言环境,但如果这不是一个表演障碍,那么 C++17 已经有了你想要的东西:std::from_chars

所有整数和浮点类型都有重载,接口是相同的,除了最后一个参数分别对整数和浮点类型不同(但如果默认是好的,那么你不需要改变任何东西)。因为这不是区域设置感知功能,所以它也非常快。它将击败任何其他字符串到值转换函数,并且通常是数量级。

关于 <charconv> (the header from_chars lives in) by Stephan T. Lavavej that you can watch about its usage and performance here: https://www.youtube.com/watch?v=4P_kbF0EbZM

有一个非常好的 CPPCON 视频

你不会得到太多,因为在像

这样的表达式中
int x = sto("1");

没有(简单的)方法来推断模板参数的所需类型。你必须写

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

这在某种程度上违背了提供通用功能的目的。另一方面,

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

正如您所意识到的那样会有用。在 C++17 中有 std::from_chars,它或多或少地完成了这一点(它不是模板而是一组重载,它采用指向字符而不是字符串的指针,但这只是次要细节)。

PS 没有简单的方法可以在上面的表达式中推断出所需的类型,但是有一种方法。我不认为你问题的核心正是你要求的签名,我不认为以下是实现它的好方法,但我知道有一种方法可以使上面的 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);,即很容易使用错误。

与所有(甚至像 C++-98 这样的旧 C++ 编译器)兼容的解决方案是使用 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

在旧的 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,并且非常通用。根据我的经验,这种方法很可靠,但不是最高效的。