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


48

在C ++标准库中,有一些函数可以将字符串转换为数字类型:

stoi
stol
stoll
stoul
stoull
stof
stod
stold

但是我发现在模板代码中使用它们很麻烦。为什么没有模板功能,例如:

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

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

我看不出没有它们的任何技术原因,但也许我缺少了一些东西。它们可以专门用于调用基础的命名函数,并使用enable_if/ concepts禁用非数字类型。

标准库中是否有任何模板友好的替代方法,可以将字符串转换为数字类型,并且以有效的方式将其转换?


这回答了你的问题了吗?为什么`std :: sto` ...系列不是模板?
Boiethios

1
@Boiethios并非如此-该问题的答案解释了“为什么”背后的原理,但它们并没有像已接受的答案那样的实用解决方案。我已编辑问题,要求其他选择以更好地陈述我的需求
Mircea Ispas

Answers:


40

为什么没有模板功能,例如:

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

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


5
C ++现在已经解构了吗?:o 结构化绑定声明
亚历山大–恢复莫妮卡

1
当然!它甚至可以使用简单的结构,或者如果提供了正确的接口,也可以使用类。
纪尧姆·拉西科特

13

它不是模板,并且不适用于语言环境,但是如果这不是显示停止器,则C ++ 17已经具有您想要的功能: std::from_chars

所有整数和浮点类型都有重载,并且接口是相同的,除了最后一个参数分别不同于整数和浮点类型(但是如果默认值很好,则您不需要更改任何内容)。由于这不是支持区域设置的功能,因此它也非常快。它将击败任何其他字符串到值的转换函数,通常它的大小为几个数量级。

斯蒂芬·拉瓦维(Stephan T.Lavavej)播放了一段很好的CPPCON视频<charconv>(标题from_chars位于其中),您可以在此处观看其用法和性能:https : //www.youtube.com/watch?v= 4P_kbF0EbZM


1
@NathanOliver:stoi及其朋友(问题中提到的转换)也不适用于语言环境,因此这不是一个热门话题。
皮特·贝克尔

9

您不会获得太多收益,因为在类似

int x = sto("1");

没有(简便)的方法来推断模板参数的所需类型。你将不得不写

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

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

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

如您所知,将很有用。在C ++ 17中std::from_chars,它或多或少地做到了这一点(它不是模板,而是一组重载,它使用指向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);,即很容易使用错误。


我认为在这里依靠隐式转换是一个好主意。尝试禁用自动是一个问题。通常,我已经看到通过将私有const引用放在仅由有效方法初始化的类中来完成此操作。不过,我在这里看不到如何利用它,因为我们在继续之前必须构造一个整个转换器对象。嗯....
bremen_matt

尽管存在非推断的类型参数,我仍然可以看到价值-正如问题所表明的那样,其动机是能够在模板代码中使用,而在模板代码中,您正在转换为实例化之间不同的类型。
Toby Speight

基本的问题是auto x = sto(s)什么?由于converter::x是超出范围的引用,因此该特定实现被破坏了,但是可以修复。只需删除引用并依靠std::string的移动语义即可。
MSalters

@MSalters是的,这是我认为有问题的参考,但是您是对的,不需要使用参考。更令我困扰的是,它似乎是一个函数,但是实际的功能在中converter,而且我不确定使用模板转换运算符是否是最佳选择,这些问题是否可以解决。也许不是我最初想的那么糟
idclev 463035818

我认为这里的const引用没有任何问题。我的理解是,直至变频器被破坏(常量引用将保留该字符串的寿命herbsutter.com/2008/01/01/...
bremen_matt


3

在较旧的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中有效,非常通用。以我的经验,这种方法很健壮,但性能不是最好。


是的,这是我所使用的,但是性能低于某些有时不希望使用的功能
Mircea Ispas
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.