Search code examples
c++boost

C++ boost locale + lexical cast throws an exception


boost::locale::generator gen;   
std::locale loc = gen("de_DE.UTF-8");
std::locale::global(loc);

std::string s = "3,14";
double d = boost::lexical_cast<double>(s);
std::cout << d << std::endl;

Why does this code throw an exception? Boost Locale does not affect lexical cast?

I want to use a comma as a separator, not a period.


Solution

  • Boost Locale doesn't apply to any number. You have to tell it using IO manipulators. E.g.:

    Live On Coliru

    #include <boost/lexical_cast.hpp>
    #include <boost/locale.hpp>
    #include <iostream>
    
    namespace as = boost::locale::as;
    
    int main() {
        boost::locale::generator gen;
        auto de = gen("de_DE.utf8");
        // std::locale::global(de);
    
        std::cout.imbue(de);
        std::cin.imbue(de);
    
        std::cout << 134.45 << std::endl;
        std::cout << as::number << 134.45 << std::endl;
        std::cout << as::currency << 134.45 << std::endl;
    }
    

    Prints

    134.45
    134,45
    134,45 €
    

    So, it stands to reason that the following will work: Live On Coliru

    if (double punkt; is("123.45") >> punkt)
         std::cout << "Punkt " << punkt << "\n";
    else std::cout << "Punkt failed\n";
    
    if (double komma; is("123,45") >> komma)
         std::cout << "Komma " << komma << " (OOPS!)\n";
    else std::cout << "Komma failed\n";
    
    if (double nummer; is("123,45") >> as::number >> nummer)
         std::cout << "Nummer " << nummer << "\n";
    else std::cout << "Nummer failed\n";
    

    Prints (note the OOPS case):

    Punkt 123,45 €
    Komma 123,00 € (OOPS!)
    Nummer 123,45 €
    

    Making It Work?

    Even if you do set the global locale, lexical_cast will not accept the manipulators. But Boost Convert does:

    The original hope was to see boost::lexical_cast extended to be applicable to a wider range of deployment scenarios. However, after discussions with Kevlin Henney (the boost::lexical_cast author) and in the Boost Developers forum it was collectively decided that the desired extensions were not compatible with the original design and the idea of what boost::lexical_cast embodied and, therefore, a new component with richer interface and functionality was needed. That decision resulted in the development of Boost.Convert described in this document.

    The 30s introduction I'd give: Live On Coliru

    #include <boost/convert.hpp>
    #include <boost/convert/stream.hpp>
    #include <boost/locale.hpp>
    #include <iostream>
    
    namespace cnv = boost::cnv;
    
    int main() {
        boost::locale::generator gen;
        std::locale::global(gen("de_DE.utf8"));
    
        cnv::cstream cnv;
        cnv(boost::locale::as::number);
        auto from_string = cnv::apply<double>(std::ref(cnv));
        auto to_string   = cnv::apply<std::string>(std::ref(cnv));
    
        std::cout << "Parsed "    << from_string("123,45") << "\n";
        std::cout << "Formatted " << to_string(123.45)     << "\n";
    }
    

    But, Lexical Cast!

    Of course, it is quite convenient to have lexical_cast Just Work(TM). You might introduce a light-weight wrapper type:

    Live

    #include <boost/lexical_cast.hpp>
    #include <boost/locale.hpp>
    #include <iostream>
    
    namespace as = boost::locale::as;
    
    struct Number {
        double value_;
        Number(double init = {}) : value_(init) {}
        operator double() const { return value_; }
    
        friend std::istream& operator>>(std::istream& is, Number& num) {
            return is >> as::number >> num.value_ >> as::posix;
        }
        friend std::ostream& operator<<(std::ostream& os, Number const& num) {
            return os << as::number << num.value_ << as::posix;
        }
    };
    
    int main() {
        boost::locale::generator gen;
        std::locale::global(gen("de_DE.utf8"));
    
        std::cout << "Formatted: " << boost::lexical_cast<std::string>(Number{234.56}) << "\n";
        double parsed = boost::lexical_cast<Number>("345,67");
        std::cout << "Parsed: " << parsed << "\n";
    }
    

    Prints

    Formatted: 234,56
    Parsed: 345.67
    

    Note that you might even evolve Number to be a vocabulary type, so you won't have to wrap it for conversions.