Std::Lexical_Cast - Is There Such a Thing

std::lexical_cast - is there such a thing?

Only partially.

C++11 <string> has std::to_string for the built-in types:

[n3290: 21.5/7]:

string to_string(int val);
string to_string(unsigned val);
string to_string(long val);
string to_string(unsigned long val);
string to_string(long long val);
string to_string(unsigned long long val);
string to_string(float val);
string to_string(double val);
string to_string(long double val);

Returns: Each function returns a string object holding the
character representation of the value of its argument that would
be generated by calling sprintf(buf, fmt, val) with a format
specifier of "%d", "%u", "%ld", "%lu", "%lld", "%llu",
"%f", "%f", or "%Lf", respectively, where buf designates
an internal character buffer of sufficient size.

There are also the following that go the other way around:

[n3290: 21.5/1, 21.5/4]:

int stoi(const string& str, size_t *idx = 0, int base = 10);
long stol(const string& str, size_t *idx = 0, int base = 10);
unsigned long stoul(const string& str, size_t *idx = 0, int base = 10);
long long stoll(const string& str, size_t *idx = 0, int base = 10);
unsigned long long stoull(const string& str, size_t *idx = 0, int base = 10);
float stof(const string& str, size_t *idx = 0);
double stod(const string& str, size_t *idx = 0);
long double stold(const string& str, size_t *idx = 0);

However, there's nothing generic that you can use (at least not until TR2, maybe!), and nothing at all in C++03.

Boost's lexical_cast From double to string Precision

The boost code ends up here:

            bool shl_real_type(double val, char* begin) {
using namespace std;
finish = start +
#if defined(_MSC_VER) && (_MSC_VER >= 1400) && !defined(__SGI_STL_PORT) && !defined(_STLPORT_VERSION)
sprintf_s(begin, CharacterBufferSize,
#else
sprintf(begin,
#endif
"%.*g", static_cast<int>(boost::detail::lcast_get_precision<double>()), val);
return finish > start;
}

You're in luck since the precision is USUALLY compile-time constant (unless boost configures BOOST_LCAST_NO_COMPILE_TIME_PRECISION).

Simplifying a bit and allowing for conforming, modern standard libraries:

Mimicking Boost Lexicalcast

#include <cstdio>
#include <limits>
#include <string>

namespace {
template <class T> struct lcast_precision {
typedef std::numeric_limits<T> limits;

static constexpr bool use_default_precision = !limits::is_specialized || limits::is_exact;
static constexpr bool is_specialized_bin = !use_default_precision && limits::radix == 2 && limits::digits > 0;

static constexpr bool is_specialized_dec = !use_default_precision && limits::radix == 10 && limits::digits10 > 0;
static constexpr unsigned int precision_dec = limits::digits10 + 1U;
static constexpr unsigned long precision_bin = 2UL + limits::digits * 30103UL / 100000UL;

static constexpr unsigned value = is_specialized_bin
? precision_bin
: is_specialized_dec? precision_dec : 6;
};

std::string mimicked(double v) {
constexpr int prec = static_cast<int>(lcast_precision<double>::value);

std::string buf(prec+10, ' ');
buf.resize(sprintf(&buf[0], "%.*g", prec, v));
return buf;
}
}

Regression Tests

To compare the results and check the assumptions:

Live On Coliru

#include <cstdio>
#include <limits>
#include <string>

namespace {
template <class T> struct lcast_precision {
typedef std::numeric_limits<T> limits;

static constexpr bool use_default_precision = !limits::is_specialized || limits::is_exact;
static constexpr bool is_specialized_bin = !use_default_precision && limits::radix == 2 && limits::digits > 0;

static constexpr bool is_specialized_dec = !use_default_precision && limits::radix == 10 && limits::digits10 > 0;
static constexpr unsigned int precision_dec = limits::digits10 + 1U;
static constexpr unsigned long precision_bin = 2UL + limits::digits * 30103UL / 100000UL;

static constexpr unsigned value = is_specialized_bin
? precision_bin
: is_specialized_dec? precision_dec : 6;
};

std::string mimicked(double v) {
constexpr int prec = static_cast<int>(lcast_precision<double>::value);

std::string buf(prec+10, ' ');
buf.resize(sprintf(&buf[0], "%.*g", prec, v));
return buf;
}
}

#include <cmath>
#include <iomanip>
#include <iostream>
#include <string>

#include <boost/lexical_cast.hpp>

#ifdef BOOST_LCAST_NO_COMPILE_TIME_PRECISION
#error BOOM
#endif

#define TEST(x) \
do { \
std::cout << std::setw(45) << #x << ":\t" << (x) << "\n"; \
} while (0)

std::string use_sprintf(double v) {
std::string buf(32, ' ');
buf.resize(std::sprintf(&buf[0], "%f", v));
return buf;
}

void tests() {
for (double v : {
std::numeric_limits<double>::quiet_NaN(),
std::numeric_limits<double>::infinity(),
-std::numeric_limits<double>::infinity(),
0.0,
-0.0,
std::numeric_limits<double>::epsilon(),
M_PI })
{
TEST(v);
TEST(std::to_string(v));
TEST(use_sprintf(v));
TEST(boost::lexical_cast<std::string>(v));
TEST(mimicked(v));

assert(mimicked(v) == boost::lexical_cast<std::string>(v));
}
}

static std::locale DE("de_DE.utf8");

int main() {

tests();

std::cout << "==== imbue std::cout\n";
std::cout.imbue(DE);

tests();

std::cout << "==== override global locale\n";
std::locale::global(DE);

tests();
}

Prints

                                        v:  nan
std::to_string(v): nan
use_sprintf(v): nan
boost::lexical_cast<std::string>(v): nan
mimicked(v): nan
v: inf
std::to_string(v): inf
use_sprintf(v): inf
boost::lexical_cast<std::string>(v): inf
mimicked(v): inf
v: -inf
std::to_string(v): -inf
use_sprintf(v): -inf
boost::lexical_cast<std::string>(v): -inf
mimicked(v): -inf
v: 0
std::to_string(v): 0.000000
use_sprintf(v): 0.000000
boost::lexical_cast<std::string>(v): 0
mimicked(v): 0
v: -0
std::to_string(v): -0.000000
use_sprintf(v): -0.000000
boost::lexical_cast<std::string>(v): -0
mimicked(v): -0
v: 2.22045e-16
std::to_string(v): 0.000000
use_sprintf(v): 0.000000
boost::lexical_cast<std::string>(v): 2.2204460492503131e-16
mimicked(v): 2.2204460492503131e-16
v: 3.14159
std::to_string(v): 3.141593
use_sprintf(v): 3.141593
boost::lexical_cast<std::string>(v): 3.1415926535897931
mimicked(v): 3.1415926535897931
==== imbue std::cout
v: nan
std::to_string(v): nan
use_sprintf(v): nan
boost::lexical_cast<std::string>(v): nan
mimicked(v): nan
v: inf
std::to_string(v): inf
use_sprintf(v): inf
boost::lexical_cast<std::string>(v): inf
mimicked(v): inf
v: -inf
std::to_string(v): -inf
use_sprintf(v): -inf
boost::lexical_cast<std::string>(v): -inf
mimicked(v): -inf
v: 0
std::to_string(v): 0.000000
use_sprintf(v): 0.000000
boost::lexical_cast<std::string>(v): 0
mimicked(v): 0
v: -0
std::to_string(v): -0.000000
use_sprintf(v): -0.000000
boost::lexical_cast<std::string>(v): -0
mimicked(v): -0
v: 2,22045e-16
std::to_string(v): 0.000000
use_sprintf(v): 0.000000
boost::lexical_cast<std::string>(v): 2.2204460492503131e-16
mimicked(v): 2.2204460492503131e-16
v: 3,14159
std::to_string(v): 3.141593
use_sprintf(v): 3.141593
boost::lexical_cast<std::string>(v): 3.1415926535897931
mimicked(v): 3.1415926535897931
==== override global locale
v: nan
std::to_string(v): nan
use_sprintf(v): nan
boost::lexical_cast<std::string>(v): nan
mimicked(v): nan
v: inf
std::to_string(v): inf
use_sprintf(v): inf
boost::lexical_cast<std::string>(v): inf
mimicked(v): inf
v: -inf
std::to_string(v): -inf
use_sprintf(v): -inf
boost::lexical_cast<std::string>(v): -inf
mimicked(v): -inf
v: 0
std::to_string(v): 0,000000
use_sprintf(v): 0,000000
boost::lexical_cast<std::string>(v): 0
mimicked(v): 0
v: -0
std::to_string(v): -0,000000
use_sprintf(v): -0,000000
boost::lexical_cast<std::string>(v): -0
mimicked(v): -0
v: 2,22045e-16
std::to_string(v): 0,000000
use_sprintf(v): 0,000000
boost::lexical_cast<std::string>(v): 2,2204460492503131e-16
mimicked(v): 2,2204460492503131e-16
v: 3,14159
std::to_string(v): 3,141593
use_sprintf(v): 3,141593
boost::lexical_cast<std::string>(v): 3,1415926535897931
mimicked(v): 3,1415926535897931

Note that mimicked and boost::lexical_cast<std::string>(double) result in exactly the same output each time.

Extended boost::lexical_cast for other class datatypes

You can define a specialization of boost::lexical_cast for the types that you want to convert.

Toy example:

typedef struct { int x; int y; } Point;

namespace boost {
template<>
std::string lexical_cast(const Point& arg) { return "2,3"; }
}

int main () {
std::cout << boost::lexical_cast<std::string>(Point ()) << std::endl;
}

prints 2,3.

Going from a string to a point requires a bit more work, but you can see how to do it.

Unexpected results from boost::lexical_castint with boost::iterator_range

The short answer is number 2 from your list, misuse of iterator_range - specifically you're using it without explicitly including the proper header for it.

Adding this:

#include <boost/range/iterator_range.hpp>

will make it behave as you expect.

The iterator_range and related functionality is split into two headers, iterator_range_core.hpp and iterator_range_io.hpp. The first one contains the class definition, the second one contains, among other things, the operator<< overload which makes it streamable and so usable by lexical_cast (usable in the sense that it will actually work as you expect).

Because you didn't included the proper header, you should normally get a compiler error, but in this case you're not getting it because lexical_cast.hpp includes the first of those two headers, iterator_range_core.hpp. This makes everything build fine, but it doesn't get the operator<< from the second header. Without that overload, when lexical_cast writes the range to the stream to perform the conversion, the best overload it finds is the one taking a bool parameter (because iterator_range has a default conversion to bool). That's why you're seeing that 1, because it's actually writing true to the underlying conversion stream.

You can test this easily with something like this:

auto someRange = boost::make_iterator_range(first, last);
std::cout << std::boolalpha<< someRange;

Without #include <boost/range/iterator_range.hpp> this will print true, with that include it will print your string (80).

Is boost::lexical_cast redundant with c++11 stoi, stof and family?

boost::lexical_cast

  • handles more kinds of conversion, including iterator pairs, arrays, C strings, etc.
  • offers the same generic interface (sto* have different names for different types)
  • is locale-sensitive (sto*/to_string are only in part, e.g. lexical_cast can process thousands separators, while stoul usually doesn't)

C++ boost lexical_cast with template?

CString does not have any operator<<
Consider using std::string

What overhead is there in performing an identity boost::lexical_cast?

Since the documentation doesn't offer anything on this topic, I dug into the lexical_cast source (1.51.0) and found that it does some compile-time checking on the types and decides a specific "caster class" that does the conversion. In case source and target are the same, this "caster class" will simply return the input.

Pseudo-codified and simplified from source (boost/lexical_cast.hpp:2268):

template <typename Target, typename Source>
Target lexical_cast(const Source &arg)
{
static if( is_character_type_to_character_type<Target, src> ||
is_char_array_to_stdstring<Target, src> ||
is_same_and_stdstring<Target, src> )
// ^-- optimization for std::string to std::string and similar stuff
{
return arg;
}
else
{
/* some complicated stuff */
}
}

I can't directly see any optimizations for other identity casts, though, and looking through the normally selected lexical_cast_do_cast "caster class" is making my head hurt. :(

Adding type for conversion by boost::lexical_cast (segfault)

It sounds like the crash is due to a stack overflow. I think your operator>> is calling itself, by implicitly converting foo.str back into a Foo.

Try removing the implicit conversion by making that constructor explicit:

explicit Foo(const std::string& str) noexcept
: str(str)
{
}

Is there a boost lexical_cast equivalent of C# TryParse?

If you can use boost, then you could use boost::conversion::try_lexical_convert:

#include <boost/lexical_cast/try_lexical_convert.hpp>

std::string str("1.2");
double res;
if(boost::conversion::try_lexical_convert<double>(str, res)){
//everything normal
}
else{
//we got a problem
}


Related Topics



Leave a reply



Submit