Reimplement lexical_cast
The new implementation doesn't drop fractional part of floating point numbers, and handles overflow correctly.
This commit is contained in:
parent
8a80af7ace
commit
bfeea42938
2 changed files with 147 additions and 44 deletions
|
@ -48,6 +48,7 @@
|
|||
#include "global.hpp"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <type_traits>
|
||||
|
@ -202,15 +203,10 @@ struct lexical_caster<
|
|||
{
|
||||
DEBUG_THROW("specialized - To long long - From (const) char*");
|
||||
|
||||
char* endptr;
|
||||
long long res = strtoll(value, &endptr, 10);
|
||||
|
||||
if (*value == '\0' || *endptr != '\0') {
|
||||
if(fallback) { return fallback.get(); }
|
||||
|
||||
throw bad_lexical_cast();
|
||||
if(fallback) {
|
||||
return lexical_cast_default<long long>(std::string(value), fallback.get());
|
||||
} else {
|
||||
return res;
|
||||
return lexical_cast<long long>(std::string(value));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -232,10 +228,16 @@ struct lexical_caster<
|
|||
{
|
||||
DEBUG_THROW("specialized - To long long - From std::string");
|
||||
|
||||
try {
|
||||
return std::stoll(value);
|
||||
} catch(std::invalid_argument&) {
|
||||
} catch(std::out_of_range&) {
|
||||
}
|
||||
|
||||
if(fallback) {
|
||||
return lexical_cast_default<long long>(value.c_str(), fallback.get());
|
||||
return fallback.get();
|
||||
} else {
|
||||
return lexical_cast<long long>(value.c_str());
|
||||
throw bad_lexical_cast();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -249,7 +251,7 @@ template <class To, class From>
|
|||
struct lexical_caster<
|
||||
To
|
||||
, From
|
||||
, typename std::enable_if<std::is_signed<To>::value && !std::is_same<To, long long>::value >::type
|
||||
, typename std::enable_if<std::is_integral<To>::value && std::is_signed<To>::value && !std::is_same<To, long long>::value >::type
|
||||
, typename std::enable_if<boost::mpl::has_key<boost::mpl::set<
|
||||
char*, const char*> , From>::value >::type
|
||||
>
|
||||
|
@ -258,15 +260,10 @@ struct lexical_caster<
|
|||
{
|
||||
DEBUG_THROW("specialized - To signed - From (const) char*");
|
||||
|
||||
char* endptr;
|
||||
int res = strtol(value, &endptr, 10);
|
||||
|
||||
if (*value == '\0' || *endptr != '\0') {
|
||||
if(fallback) { return fallback.get(); }
|
||||
|
||||
throw bad_lexical_cast();
|
||||
if(fallback) {
|
||||
return lexical_cast_default<To>(std::string(value), fallback.get());
|
||||
} else {
|
||||
return res;
|
||||
return lexical_cast<To>(std::string(value));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -280,17 +277,94 @@ template <class To>
|
|||
struct lexical_caster<
|
||||
To
|
||||
, std::string
|
||||
, typename std::enable_if<std::is_signed<To>::value >::type
|
||||
, typename std::enable_if<std::is_integral<To>::value && std::is_signed<To>::value && !std::is_same<To, long long>::value >::type
|
||||
>
|
||||
{
|
||||
To operator()(const std::string& value, boost::optional<To> fallback)
|
||||
{
|
||||
DEBUG_THROW("specialized - To signed - From std::string");
|
||||
|
||||
try {
|
||||
long res = std::stol(value);
|
||||
if(std::numeric_limits<To>::lowest() <= res && std::numeric_limits<To>::max() >= res) {
|
||||
return static_cast<To>(res);
|
||||
}
|
||||
} catch(std::invalid_argument&) {
|
||||
} catch(std::out_of_range&) {
|
||||
}
|
||||
|
||||
if(fallback) {
|
||||
return lexical_cast_default<To>(value.c_str(), fallback.get());
|
||||
return fallback.get();
|
||||
} else {
|
||||
return lexical_cast<To>(value.c_str());
|
||||
throw bad_lexical_cast();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Specialized conversion class.
|
||||
*
|
||||
* Specialized for returning a floating point type from a (const) char*.
|
||||
*/
|
||||
template <class To, class From>
|
||||
struct lexical_caster<
|
||||
To
|
||||
, From
|
||||
, typename std::enable_if<std::is_floating_point<To>::value >::type
|
||||
, typename std::enable_if<boost::mpl::has_key<boost::mpl::set<
|
||||
char*, const char*> , From>::value >::type
|
||||
>
|
||||
{
|
||||
To operator()(From value, boost::optional<To> fallback)
|
||||
{
|
||||
DEBUG_THROW("specialized - To floating point - From (const) char*");
|
||||
|
||||
if(fallback) {
|
||||
return lexical_cast_default<To>(std::string(value), fallback.get());
|
||||
} else {
|
||||
return lexical_cast<To>(std::string(value));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Specialized conversion class.
|
||||
*
|
||||
* Specialized for returning a floating point type from a std::string.
|
||||
*/
|
||||
template <class To>
|
||||
struct lexical_caster<
|
||||
To
|
||||
, std::string
|
||||
, typename std::enable_if<std::is_floating_point<To>::value >::type
|
||||
>
|
||||
{
|
||||
To operator()(const std::string& value, boost::optional<To> fallback)
|
||||
{
|
||||
DEBUG_THROW("specialized - To floating point - From std::string");
|
||||
|
||||
// Explicitly reject hexadecimal values. Unit tests of the config class require that.
|
||||
if(value.find_first_of("Xx") != std::string::npos) {
|
||||
if(fallback) {
|
||||
return fallback.get();
|
||||
} else {
|
||||
throw bad_lexical_cast();
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
long double res = std::stold(value);
|
||||
if(std::numeric_limits<To>::lowest() <= res && std::numeric_limits<To>::max() >= res) {
|
||||
return static_cast<To>(res);
|
||||
}
|
||||
} catch(std::invalid_argument&) {
|
||||
} catch(std::out_of_range&) {
|
||||
}
|
||||
|
||||
if(fallback) {
|
||||
return fallback.get();
|
||||
} else {
|
||||
throw bad_lexical_cast();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -311,20 +385,15 @@ struct lexical_caster<
|
|||
char*, const char*> , From>::value >::type
|
||||
>
|
||||
{
|
||||
long long operator()(From value, boost::optional<unsigned long long> fallback)
|
||||
unsigned long long operator()(From value, boost::optional<unsigned long long> fallback)
|
||||
{
|
||||
DEBUG_THROW(
|
||||
"specialized - To unsigned long long - From (const) char*");
|
||||
|
||||
char* endptr;
|
||||
unsigned long long res = strtoull(value, &endptr, 10);
|
||||
|
||||
if (*value == '\0' || *endptr != '\0') {
|
||||
if(fallback) { return fallback.get(); }
|
||||
|
||||
throw bad_lexical_cast();
|
||||
if(fallback) {
|
||||
return lexical_cast_default<unsigned long long>(std::string(value), fallback.get());
|
||||
} else {
|
||||
return res;
|
||||
return lexical_cast<unsigned long long>(std::string(value));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -342,14 +411,20 @@ struct lexical_caster<
|
|||
, std::string
|
||||
>
|
||||
{
|
||||
long long operator()(const std::string& value, boost::optional<unsigned long long> fallback)
|
||||
unsigned long long operator()(const std::string& value, boost::optional<unsigned long long> fallback)
|
||||
{
|
||||
DEBUG_THROW("specialized - To unsigned long long - From std::string");
|
||||
|
||||
try {
|
||||
return std::stoull(value);
|
||||
} catch(std::invalid_argument&) {
|
||||
} catch(std::out_of_range&) {
|
||||
}
|
||||
|
||||
if(fallback) {
|
||||
return lexical_cast_default<unsigned long long>(value.c_str(), fallback.get());
|
||||
return fallback.get();
|
||||
} else {
|
||||
return lexical_cast<unsigned long long>(value.c_str());
|
||||
throw bad_lexical_cast();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -372,15 +447,10 @@ struct lexical_caster<
|
|||
{
|
||||
DEBUG_THROW("specialized - To unsigned - From (const) char*");
|
||||
|
||||
char* endptr;
|
||||
int res = strtoul(value, &endptr, 10);
|
||||
|
||||
if (*value == '\0' || *endptr != '\0') {
|
||||
if(fallback) { return fallback.get(); }
|
||||
|
||||
throw bad_lexical_cast();
|
||||
if(fallback) {
|
||||
return lexical_cast_default<To>(std::string(value), fallback.get());
|
||||
} else {
|
||||
return res;
|
||||
return lexical_cast<To>(std::string(value));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -401,10 +471,20 @@ struct lexical_caster<
|
|||
{
|
||||
DEBUG_THROW("specialized - To unsigned - From std::string");
|
||||
|
||||
try {
|
||||
unsigned long res = std::stoul(value);
|
||||
// No need to check the lower bound, it's zero for all unsigned types.
|
||||
if(std::numeric_limits<To>::max() >= res) {
|
||||
return static_cast<To>(res);
|
||||
}
|
||||
} catch(std::invalid_argument&) {
|
||||
} catch(std::out_of_range&) {
|
||||
}
|
||||
|
||||
if(fallback) {
|
||||
return lexical_cast_default<To>(value.c_str(), fallback.get());
|
||||
return fallback.get();
|
||||
} else {
|
||||
return lexical_cast<To>(value.c_str());
|
||||
throw bad_lexical_cast();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -196,6 +196,28 @@ BOOST_AUTO_TEST_CASE(test_lexical_cast_unsigned_long_long)
|
|||
std::string(value)), const char*, validate);
|
||||
}
|
||||
|
||||
typedef boost::mpl::vector<
|
||||
float
|
||||
, double
|
||||
, long double> test_lexical_cast_floating_point_types;
|
||||
|
||||
BOOST_AUTO_TEST_CASE_TEMPLATE(
|
||||
test_lexical_cast_floating_point, T, test_lexical_cast_floating_point_types)
|
||||
{
|
||||
result = "specialized - To floating point - From (const) char*";
|
||||
|
||||
const char* value = "test";
|
||||
BOOST_CHECK_EXCEPTION(lexical_cast<T>(
|
||||
value), const char*, validate);
|
||||
BOOST_CHECK_EXCEPTION(lexical_cast<T>(
|
||||
const_cast<char*>(value)), const char*, validate);
|
||||
|
||||
result = "specialized - To floating point - From std::string";
|
||||
|
||||
BOOST_CHECK_EXCEPTION(lexical_cast<T>(
|
||||
std::string(value)), const char*, validate);
|
||||
}
|
||||
|
||||
} // namespace test_throw
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_lexical_cast_result)
|
||||
|
@ -213,6 +235,7 @@ BOOST_AUTO_TEST_CASE(test_lexical_cast_result)
|
|||
BOOST_CHECK_EQUAL(lexical_cast<int>("-1"), -1);
|
||||
BOOST_CHECK_EQUAL(lexical_cast<unsigned>("1"), 1);
|
||||
BOOST_CHECK_EQUAL(lexical_cast<double>("1.2"), 1.2);
|
||||
BOOST_CHECK_THROW(lexical_cast<double>("0x11"), bad_lexical_cast);
|
||||
|
||||
std::string a = "01234567890123456789";
|
||||
BOOST_CHECK_EQUAL(lexical_cast<long long>(a), 1234567890123456789ll);
|
||||
|
|
Loading…
Add table
Reference in a new issue