将字符串转换为整数类型T,检查是否溢出

时间:2018-07-29 09:09:28

标签: c++ templates string-conversion

目标是一个函数,给定一个包含数字的字符串和整数类型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 !

此类功能由例如D中的std.conv.to和Rust中的str::parse。什么是C ++等价物?

2 个答案:

答案 0 :(得分:2)

您的解决方案似乎还不错。但是,如果要更好地优化编译时间,则应考虑进行模板专门化。在给出的代码中,您根据类型进行分支。但是,可以将其编译到代码中,而不是在编译时(已经可以解决)解决该问题。此外,如果您要根据类型添加其他检查,则该功能很快会引起混淆。

我编写了自己的转换代码版本,除了您的转换代码外,它还检查是否对整数类型给出了科学的记号:

#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的情况。现在,此问题已解决。

答案 1 :(得分:1)

以下是使用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);
    }
}