与“std::stof”相比,编写一个没有开销的通用“string_to_float<T>”

Writing a generic `string_to_float<T>` without overhead compared to `std::stof`

提问人:0xbadf00d 提问时间:10/29/2023 更新时间:10/30/2023 访问量:174

问:

我想编写一个带有模板参数的函数,分别是 、 和 、 、 和 。我的尝试如下:string_to_floatTstring_to_float = std::stofstring_to_float = std::stodstring_to_float = std::stoldT = floatT = doubleT = long double

template<typename T>
T string_to_float(std::string const& s, std::size_t* pos = nullptr)
{
    static_assert(std::is_same_v<T, float> || std::is_same_v<T, double> || std::is_same_v<T, long double>,
        "T is not a built-in floating point type");

    if constexpr (std::is_same_v<T, float>)
        return std::stof(s, pos);
    if constexpr (std::is_same_v<T, double>)
        return std::stod(s, pos);
    if constexpr (std::is_same_v<T, long double>)
        return std::stold(s, pos);

    return T{};
}

但是,我担心这个声明。虽然在这种情况下静态断言已经失败,但我不想在默认不可构造时产生额外的误导性编译器错误。returnT

我还想确保调用 的代码确实与我使用 、 或直接使用的代码完全相同(当然,假设 ,或者 )。string_to_floatstd::stofstd::stodstd::stoldT = floatT = doubleT = long double

这就是为什么我没有删除最后一个 if 子句检查是否等于并简单地在最后一行返回的原因。另一方面,在编译时,已经很清楚是否或在这种情况下,最后一行中会有一个 pre the;因此,编译器可能会忽略它。Tlong doublestd::stold(s, pos);T = floatT = doublereturnreturnreturn

我还查看了属性说明符序列,并希望有某种属性,以便编译器真正知道永远不会到达此行以下的代码。[[unreachable]]

C++ 20 if-constexpr c++-属性

评论

0赞 Chayim Friedman 10/29/2023
您可以改用专业化。
2赞 Pepijn Kramer 10/29/2023
看看 en.cppreference.com/w/cpp/utility/from_chars,这已经有你要找的三个重载了。
1赞 user7860670 10/29/2023
“希望有某种[[无法到达]]属性” - std::无法到达出现在C++23中
0赞 Toby Speight 10/30/2023
限制模板可能是个好主意:/...#include <concepts>template<std::floating_point T>

答:

5赞 Ted Lyngmo 10/29/2023 #1

我不想在默认情况下不可构造时产生额外的误导性编译器错误。T

然后,不要包括最后一个.无论如何,它永远不会被使用。return T{};

例:

template<class T>
inline constexpr bool always_false_v = false;

template <typename T>
T string_to_float(std::string const& s, std::size_t* pos = nullptr) {
    if constexpr (std::is_same_v<T, float>)
        return std::stof(s, pos);
    else if constexpr (std::is_same_v<T, double>)
        return std::stod(s, pos);
    else if constexpr (std::is_same_v<T, long double>)
        return std::stold(s, pos);
    else 
        static_assert(always_false_v<T>,
                      "T is not a built-in floating point type");
}

根据CWG2518,不实现新规则的旧版本编译器需要变量模板,而不仅仅是变量模板。always_false_vfalse

示例(包括版本):

  • GCC 高达 12.3
  • 最多 16 个
  • ICX 至 2023.1.0
  • 即使在当前的最新版本中,MSVC 仍然无法处理。false

评论

0赞 Toby Speight 10/30/2023
Ted,你为什么要使用而不是直接使用? 确保删除代码,除非没有先前的条件匹配。always_false_v<T>falseif constexpr
1赞 Ted Lyngmo 10/30/2023
@TobySpeight 并非所有编译器都实现了这一点,所以他们需要这种迂回的方式来制作它。例如,我认为 clang 到 ver. 16 会失败。我补充了一些例子。false
2赞 Jeff Garrett 10/30/2023
@TobySpeight CWG2518 2023 年 2 月才接受在未评估的模板上下文中允许 static_assert(false)!在此之前,如果 constexpr() {} else static_assert(false, ...) 格式不正确。CWG2518 被接受为 DR,因此编译器将其应用于所有标准模式,而不仅仅是 C++23。截至 7-8 个月前,该语言的新东西还没有出现在许多已发布的编译器版本中。
4赞 user7860670 10/29/2023 #2

您可以提供 3 个禁止通用模板的专用化,而不是静态断言和类型检查。

#include <string>

template<typename T>
T string_to_float(std::string const & s, std::size_t * pos = nullptr) = delete;

template<>
float string_to_float(std::string const & s, std::size_t * pos)
{
    return std::stof(s, pos);
}

template<>
double string_to_float(std::string const & s, std::size_t * pos)
{
    return std::stod(s, pos);
}

template<>
long double string_to_float(std::string const & s, std::size_t * pos)
{
    return std::stold(s, pos);
}

在线编译器

评论

0赞 Mark Ransom 10/29/2023
您的专业化不完整。编译器将如何选择一个?
2赞 Mark Ransom 10/29/2023
float、double 和 long double 不是参数,它们是返回值。在重载解决中不考虑返回值。
2赞 user7860670 10/30/2023
@MarkRansom 我认为您将模板专用化与重载函数混淆了。只有一个重载候选者 - 模板函数。将根据显式提供的模板参数选择此函数的匹配专用化。float、double 和 long double 确实是返回值,但它们也定义了相应专用化的模板参数。它本来可以像,但这只是有点多余。为了以防万一,我添加了 OC 示例。string_to_floattemplate<> float string_to_float<float>
1赞 Toby Speight 10/30/2023
它由调用方选择,显式指定要使用的实例化。您不妨使用三个不同的名称(例如,和 )。我们唯一获得的是,您可以从模板函数(例如)调用它。stofstodstoldtemplate<std::floating_point T> foo() { … string_to_float<T>(s)
1赞 user17732522 10/30/2023
@MarkRansom 当匹配显式专用化时,整个函数的类型会针对候选函数模板的类型执行模板参数推导,即 对。在此推导中,也可以从返回类型推导出模板参数。有点令人困惑的是,模板参数推导与函数调用中的推导不同,而函数调用中的参数/参数类型对是单独匹配的,但这是有道理的,因为函数调用中没有先验返回类型。float(std::string const&, std::size_t*)T(std::string const&, std::size_t*)