lexical cast

From Cppwiki

Jump to: navigation, search

Standard C++ does not provide a general-purpose utility function to convert between disparate data types. The Boost library provides boost::lexical_cast, which is perhaps the canonical solution to this problem. You should use it if you can.

Alternatively, you can use the C++ stringstream class to perform the conversion, although it's a bit tricky. This is the simplest thing that could possibly work, if you don't care at all about error handling:

#include <sstream>
#include <string>
#include <cassert>

const int answer = 42;

int main() {
    std::string answer_s;
    { std::stringstream ss; ss << answer; ss >> answer_s; }
    int target;
    { std::stringstream ss; ss << answer_s; ss >> target; }
    assert(target == answer);
}

You could then wrap the interesting bits into a function:

#include <sstream>

template <class Target, class Source>
Target lexical_cast(const Source &source) {
    Target target;

    std::stringstream ss;
    ss << source;
    ss >> target;
        
    return target;
}

#include <string>
#include <cassert>

const int answer = 42;

int main() {
    const std::string answer_s = lexical_cast<std::string>(answer);
    assert(lexical_cast<int>(answer_s) == answer);
}

This approach has some problems:

  • Behavior of conversion to pointer-to-char is undefined -- it may crash your program.
 lexical_cast<char *>(answer) // KABOOM!
That's because it ends up trying to store input into some random address in memory:
 char *target; // 'target' refers to some (unknown) address in memory
 ss >> target; // read from 'ss' and store result into memory at that (unknown) address
  • There's no way to tell if the conversion was successful:
 std::cerr << lexical_cast<int>("3.14") << '\n';       // output: 3
 std::cerr << lexical_cast<int>("not an int") << '\n'; // output: unknown
You might consider the conversion as a failure if it only read part of the data (e.g. the "3" in "3.14").

A better implementation would have the following properties:

  • It uses concept checking to generate a C++ compilation error if the target is a pointer.
  • It throws an exception if the conversion failed, including if any data (excluding whitespace) is left over.
    • Hint: (ss >> std::ws).ignore().good() skips whitespace, then tries to ignore a character, then checks if it worked. If it did, then something's wrong. :)
#include <sstream>
#include <typeinfo>
    
/** Disable lexical_cast to certain types, such as pointers. */
template <typename> struct LexicallyCastableTo { };
template <typename Target> struct LexicallyCastableTo<Target *>;

/** Thrown by lexical_cast if conversion fails. */
class bad_lexical_cast : public std::bad_cast {
public:
    explicit bad_lexical_cast(const char *msg) : m_msg(msg) { }
    const char *what() const throw() { return m_msg; }
    
private:
    const char *m_msg;
};

/** Similar to "target = lexical_cast<Target>(source)", but doesn't initialize nor copy an object of type Target.
 *  @throw bad_lexical_cast Failure converting Source to string or string to Target
 */
template <typename Source, typename Target>
void lexical_assign(const Source &source, Target& target) {
    std::stringstream ss;
    if (!(ss << source)) {
        throw bad_lexical_cast("failed to convert from source to string");
    } else if (!(ss >> target) || (ss >> std::ws).ignore().good()) {
        throw bad_lexical_cast("failed to convert from string to target");
    }
}
        
/** Use std::stringstream to convert @c source to type Target.
 *  @throw bad_lexical_cast Failure converting Source to string or string to Target
 */
template <typename Target, typename Source>
Target lexical_cast(const Source &source) {
    LexicallyCastableTo<Target>();
            
    Target target;
    lexical_assign(source, target);                
    return target;
}

#include <iostream>
#include <string>
#include <vector>
#include <cassert>
    
const int answer = 42;
    
int main() {
    const std::string answer_s = lexical_cast<std::string>(answer);
    assert(lexical_cast<int>(answer_s) == answer);
    
    std::vector<const char *> bad_ints;
    bad_ints.push_back("not an int");
    bad_ints.push_back("3.14");
    bad_ints.push_back("true");
    for (unsigned i = 0; i < bad_ints.size(); ++i) {
        try {
            lexical_cast<int>(bad_ints[i]);
        } catch(const bad_lexical_cast &) {
            continue;
        }
        
        assert(false && "should have thrown, caught, and continued");
    }
    
#if 0
    // invalid use of undefined type `struct LexicallyCastableTo<char*>'
    lexical_cast<char *>(answer);
#endif
}
Personal tools