【问题标题】:Convert string to integer type T, checking for overflow将字符串转换为整数类型 T,检查溢出
【发布时间】:2019-01-05 19:27:31
【问题描述】:

目标是一个函数,给定一个包含数字的字符串和一个整数类型T,如果该值符合类型且没有溢出,则返回成功 + 转换后的值,否则返回失败。

在某些情况下,使用std::istringstream 从字符串中读取数字有效:

template<typename T>
std::pair<bool, T> decode(std::string s)
{
    T value;
    std::istringstream iss(s);
    iss >> std::dec >> value;
    return std::pair<bool, T>(!iss.fail(), value);
}

template<typename T>
void testDecode(std::string s)
{
    std::pair<bool, T> result = decode<T>(s);
    if (result.first)
        std::cout << +result.second;
    else
        std::cout << "ERROR";
    std::cout << std::endl;
}

int main()
{
    testDecode<int32_t>("12"); // 12
    testDecode<int16_t>("1000000"); // ERROR
    testDecode<int16_t>("65535"); // ERROR
    return 0;
}

但是,对于 8 位类型(因为它们被视为字符),它会失败:

    testDecode<uint8_t>("12"); // 49 !

负数也被错误地接受并解析为无符号类型:

    testDecode<uint16_t>("-42"); // 65494 !

这样的功能是由例如提供的。 std.conv.to 在 D 中,str::parse 在 Rust 中。什么是 C++ 等价物?

【问题讨论】:

标签: c++ templates string-conversion


【解决方案1】:

您的解决方案似乎很好。但是,如果您想要更好的编译时优化,您应该考虑进行模板特化。在提供的代码中,您根据类型进行分支。然而,这可能会被编译到代码中,而不是在编译时解析(已经有可能)。此外,如果您想根据类型添加额外的检查,该函数很快就会变得混乱。

我编写了我自己的转换代码版本,除了你的代码之外,它还检查是否为整数类型提供了科学记数法:

#include <type_traits>
#include <utility>
#include <string>
#include <limits>
#include <algorithm>

template <typename T>
auto to_T(const std::string &s) -> std::enable_if_t<std::is_floating_point<T>::value, std::pair<bool, T>>
{
    return std::pair<bool, T>{true, T(std::stold(s))}; //read the string into the biggest floating point possible, and do a narrowing conversion
}
template <typename T>
auto to_T(const std::string &s) -> std::enable_if_t<!std::is_floating_point<T>::value && std::is_signed<T>::value, std::pair<bool, T>>
{
    return ((long long)(std::numeric_limits<T>::min()) <= std::stoll(s) && //does the integer in the string fit into the types data range?
            std::stoll(s) <= (long long)(std::numeric_limits<T>::max()))
               ? std::pair<bool, T>{true, T(std::stoll(s))}
               : std::pair<bool, T>{false, 0}; //if yes, read the string into the biggest possible integer, and do a narrowing conversion
}
template <typename T>
auto to_T(const std::string &s) -> std::enable_if_t<!std::is_floating_point<T>::value && std::is_unsigned<T>::value, std::pair<bool, T>>
{
    return ((unsigned long long)(std::numeric_limits<T>::min()) <= std::stoull(s) && //does the integer in the string fit into the types data range?
            std::stoull(s) <= (unsigned long long)(std::numeric_limits<T>::max()))
               ? std::pair<bool, T>{true, T(std::stoull(s))}
               : std::pair<bool, T>{false, 0}; //if yes, read the string into the biggest possible integer, and do a narrowing conversion
}
template <typename T>
auto decode(const std::string &s) -> std::enable_if_t<std::is_floating_point<T>::value, std::pair<bool, T>>
{
    return s.empty() ? //is the string empty?
               std::pair<bool, T>{false, 0}
                     : to_T<T>(s); //if not, convert the string to a floating point number
}
template <typename T>
auto decode(const std::string &s) -> std::enable_if_t<!std::is_floating_point<T>::value && std::is_signed<T>::value, std::pair<bool, T>>
{
    return (s.empty() ||                                                 //is the string empty?
            std::find(std::begin(s), std::end(s), '.') != std::end(s) || //or does it not fit the integer format?
            std::find(std::begin(s), std::end(s), ',') != std::end(s) ||
            std::find(std::begin(s), std::end(s), 'e') != std::end(s) ||
            std::find(std::begin(s), std::end(s), 'E') != std::end(s))
               ? std::pair<bool, T>{false, 0}
               : to_T<T>(s); //if not, convert the string to a signed integer value
}
template <typename T>
auto decode(const std::string &s) -> std::enable_if_t<!std::is_floating_point<T>::value && std::is_unsigned<T>::value, std::pair<bool, T>>
{
    return (s.empty() ||                                                 //is the string empty?
            std::find(std::begin(s), std::end(s), '.') != std::end(s) || //or does it not fit the integer format?
            std::find(std::begin(s), std::end(s), ',') != std::end(s) ||
            std::find(std::begin(s), std::end(s), 'e') != std::end(s) ||
            std::find(std::begin(s), std::end(s), 'E') != std::end(s) ||
            std::find(std::begin(s), std::end(s), '-') != std::end(s))
               ? //or does it have a sign?
               std::pair<bool, T>{false, 0}
               : to_T<T>(s); //if not, convert the string to an unsigned integer value
}

这仍然需要在平台之间进行一些移植,因为std::stoldstd::stollstd::stoull 可能不可用。但除此之外,它应该独立于平台类型的实现。

编辑:

我忘记了 decode 不应该读取数字,而是返回 0 的情况。现在已修复。

【讨论】:

    【解决方案2】:

    以下是使用istringstream 的直接方法以及解决它的注意事项:

    template<typename T>
    std::pair<bool, T> decode(std::string s)
    {
        typedef std::pair<bool, T> Result;
    
        if (s.empty())
            return Result(false, 0);
    
        if (!std::numeric_limits<T>::is_signed && s[0] == '-')
            return Result(false, 0);
    
        if (sizeof(T) == 1)
        {
            // Special case for char
            std::pair<bool, short> result = decode<short>(s);
            if (!result.first)
                return Result(false, 0);
            if (!inrange(result.second, std::numeric_limits<T>::min(), std::numeric_limits<T>::max()))
                return Result(false, 0);
            return Result(true, (T)result.second);
        }
        else
        {
            T value;
            std::istringstream iss(s);
            iss >> std::dec >> value;
            return Result(!iss.fail(), value);
        }
    }
    

    【讨论】:

    • sizeof(T) == 1 可能不适用于所有可能的情况。请改用type_traitsif (std::is_same&lt;char,T&gt;::value) 另外,你真的想在无符号类型的负输入上失败吗?只取绝对值可能更有用......此外,您正在尝试使用&lt;optional&gt; 的概念。我建议使用它而不是 pair。 (如果你可以使用 c++17。)
    • std::is_same 是不够的,因为istringstream 似乎解析了charunsigned char 的字符。也许sizeof(T) == sizeof(char)?而且,是的,负输入失败是预期的行为。如果用户在配置文件、输入等中输入了一个负值,而这不是预期的,它不应该被默默地解释为一个很大的正数。
    • 实际上,std::is_same 似乎是一个过拟合,并且即使对于 int8_t 也计算为 false(即 std::is_same&lt;char,int8_t&gt;::value 是错误的,即使 istringstream 确实将 int8_t 解析为一个字符) .
    • 你的代码有细微的错误;回想一下 C++ 只保证 sizeof(char) &lt;= sizeof(short);如果控制流达到您的第三个if(...,您将拥有无限递归,并且您在一个罕见情况下的平台上 sizeof(char) == sizeof(short)
    • char 在这个实现中根本不存在。如果sizeof(short) == 1,就会发生无限递归,你是这个意思吗?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-03-15
    相关资源
    最近更新 更多