【问题标题】:Template friendly string to numeric in C++模板友好的字符串到 C++ 中的数字
【发布时间】:2020-06-03 16:58:47
【问题描述】:

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

stoi
stol
stoll
stoul
stoull
stof
stod
stold

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

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

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

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

标准库中是否有任何模板友好的替代方法可以将字符串转换为数字类型,反之亦然?

【问题讨论】:

  • 这能回答你的问题吗? Why is the `std::sto`... series not a template?
  • @Boiethios 不是真的 - 该问题的答案解释了“为什么”背后的基本原理,但它们没有像公认的答案那样提供实际的解决方案。我已经编辑了我的问题,以要求更好地说明我需要什么

标签: c++ string-conversion


【解决方案1】:

为什么没有类似的模板函数:

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++ 现在有解构了吗? :o Structured binding declaration
  • 当然!它甚至可以使用简单的结构,或者如果给定正确的接口,也可以使用类。
  • 值得注意的是,这不适用于浮点精度,正如Reddit 中提到的以及我刚刚自己测试过的那样。
  • @PatrizioBertoni 据我所知,它应该可以与完整的 C++17 标准库一起使用。 MSVC 实现了函数的浮点版本。
【解决方案2】:

它不是一个模板,它不适用于语言环境,但如果这不是一个显示停止器,那么 C++17 已经拥有你想要的:std::from_chars

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

Stephan T. Lavavej 有一个关于 &lt;charconv&gt; 的非常好的 CPPCON 视频(标题 from_chars 位于其中),您可以在此处观看其用法和性能:https://www.youtube.com/watch?v=4P_kbF0EbZM

【讨论】:

  • @NathanOliver:stoi 及其朋友(问题中提到的转换)也不适用于语言环境,所以这不是一个很好的选择。
【解决方案3】:

你不会有太多收获,因为在这样的表达中

int x = sto("1");

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

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

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

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

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

附言 没有简单的方法可以在上面的表达式中推断出所需的类型,但是有一种方法。我不认为您问题的核心正是您要求的签名,并且我认为以下不是实现它的好方法,但我知道有一种方法可以使上述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 引用放在仅由有效方法初始化的类中来完成。我看不出在这里如何利用它,因为在继续之前我们必须构造一个完整的转换器对象。嗯....
  • 尽管没有推导类型参数,但我可以看到价值 - 正如问题所说,动机是能够从模板代码中使用,您可以在模板代码中转换为在实例之间变化的类型.
  • auto x = sto(s) 的根本问题是什么?这个特定的实现会中断,因为converter::x 是一个超出范围的引用,但这是可以修复的。只需删除引用并依赖 std::string 的移动语义。
  • @MSalters 是的,我认为这是有问题的参考,但你是对的,不需要使用参考。实际上更让我不安的是它看起来是一个函数,但实际功能在converter,我也不确定使用模板转换运算符是否是最佳选择,可以修复的事情。也许它并没有我最初想的那么糟糕
  • 我认为这里的 const 引用没有任何问题。我的理解是 const 引用将保留字符串的生命周期,直到转换器被破坏(herbsutter.com/2008/01/01/…)
【解决方案4】:

与所有(甚至是 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

【讨论】:

    【解决方案5】:

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

    【讨论】:

    • 是的,这是我用过的,但性能低于命名函数,这有时是不需要的
    猜你喜欢
    • 1970-01-01
    • 2010-12-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-10-25
    • 2011-12-06
    • 2011-04-04
    相关资源
    最近更新 更多