Skip to content

Commit

Permalink
Reimplement lexical_cast
Browse files Browse the repository at this point in the history
The new implementation doesn't drop fractional part of floating point
numbers, and handles overflow correctly.
  • Loading branch information
jyrkive committed Dec 8, 2016
1 parent 8a80af7 commit bfeea42
Show file tree
Hide file tree
Showing 2 changed files with 146 additions and 43 deletions.
166 changes: 123 additions & 43 deletions src/lexical_cast.hpp
Expand Up @@ -48,6 +48,7 @@
#include "global.hpp"

#include <cstdlib>
#include <limits>
#include <string>
#include <sstream>
#include <type_traits>
Expand Down Expand Up @@ -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));
}
}
};
Expand All @@ -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();
}
}
};
Expand All @@ -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
>
Expand All @@ -258,39 +260,111 @@ struct lexical_caster<
{
DEBUG_THROW("specialized - To signed - From (const) char*");

char* endptr;
int res = strtol(value, &endptr, 10);
if(fallback) {
return lexical_cast_default<To>(std::string(value), fallback.get());
} else {
return lexical_cast<To>(std::string(value));
}
}
};

if (*value == '\0' || *endptr != '\0') {
if(fallback) { return fallback.get(); }
/**
* Specialized conversion class.
*
* Specialized for returning a signed type from a std::string.
*/
template <class To>
struct lexical_caster<
To
, std::string
, 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 fallback.get();
} else {
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 res;
return lexical_cast<To>(std::string(value));
}
}
};

/**
* Specialized conversion class.
*
* Specialized for returning a signed type from a std::string.
* 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_signed<To>::value >::type
, 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 signed - From std::string");
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 lexical_cast_default<To>(value.c_str(), fallback.get());
return fallback.get();
} else {
return lexical_cast<To>(value.c_str());
throw bad_lexical_cast();
}
}
};
Expand All @@ -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));
}
}
};
Expand All @@ -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();
}
}
};
Expand All @@ -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));
}
}
};
Expand All @@ -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();
}
}
};
Expand Down
23 changes: 23 additions & 0 deletions src/tests/test_lexical_cast.cpp
Expand Up @@ -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)
Expand All @@ -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);
Expand Down

0 comments on commit bfeea42

Please sign in to comment.