字符串转整数类型T,检查是否溢出
Convert string to integer type T, checking for overflow
目标是一个函数,给定一个包含数字的字符串和一个整数类型 T
,returns 成功 + 转换后的值,如果该值符合类型而没有溢出,否则失败否则。
使用 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
in D and str::parse
在 Rust 中。 C++ 的等价物是什么?
下面是使用 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);
}
}
您的解决方案似乎不错。然而,如果你想要更好的编译时优化,你应该考虑做模板特化。在提供的代码中,您根据类型进行分支。然而,这可能会被编译到代码中,而不是在编译时(已经可能的情况下)被解析。此外,如果您想根据类型添加额外的检查,该功能很快就会变得混乱。
我编写了我自己的转换代码版本,除了您的之外,它还检查是否为整数类型给出了科学记数法:
#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::stold
、std::stoll
或 std::stoull
可能不可用。但除此之外,它应该独立于平台类型实现。
编辑:
我忘记了 decode
不应该读取数字,而是返回 0
的情况。现在已修复。
目标是一个函数,给定一个包含数字的字符串和一个整数类型 T
,returns 成功 + 转换后的值,如果该值符合类型而没有溢出,否则失败否则。
使用 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
in D and str::parse
在 Rust 中。 C++ 的等价物是什么?
下面是使用 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);
}
}
您的解决方案似乎不错。然而,如果你想要更好的编译时优化,你应该考虑做模板特化。在提供的代码中,您根据类型进行分支。然而,这可能会被编译到代码中,而不是在编译时(已经可能的情况下)被解析。此外,如果您想根据类型添加额外的检查,该功能很快就会变得混乱。
我编写了我自己的转换代码版本,除了您的之外,它还检查是否为整数类型给出了科学记数法:
#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::stold
、std::stoll
或 std::stoull
可能不可用。但除此之外,它应该独立于平台类型实现。
编辑:
我忘记了 decode
不应该读取数字,而是返回 0
的情况。现在已修复。