Skip to content

Desynchronized C and C++ Global Locales Break boost::lexical_cast Round-Trip #98

Description

@SudakovIvan

When std::locale::global() is set to a custom C++ locale with no name (e.g., "*"), the underlying C locale is not updated via std::setlocale(). This causes boost::lexical_cast to use different locales for output and input:

double → string uses the C locale (snprintf) — formatting with '.'

string → double uses the C++ locale (std::stringstream) — expecting '#' (or whatever the custom facet defines)

Example:

#include <boost/lexical_cast.hpp>
#include <iostream>
#include <locale>
#include <clocale>
#include <stdexcept>

// Custom facet: uses '#' as decimal point separator
class HashDecimalPoint:
  public std::numpunct<char>
{
protected:
  char do_decimal_point() const override
  {
    return '#';
  }
};

int main()
{
  try
  {
    // 1. Create C++ locale with '#' as decimal separator
    std::locale hashLocale(std::locale::classic(), new HashDecimalPoint());
    std::cout << "hashLocale name: \"" << hashLocale.name() << "\"" << std::endl;

    // 2. Set global C locale to "C" (uses '.' as decimal separator)
    if(!std::setlocale(LC_ALL, "C"))
    {
      std::cerr << "Failed to set C locale" << std::endl;
      return 1;
    }
    std::cout << "C locale set to: " << std::setlocale(LC_ALL, nullptr) << std::endl;

    // 3. Set global C++ locale to hashLocale
    // IMPORTANT: hashLocale.name() == "*", so std::locale::global() does NOT call setlocale()
    std::locale::global(hashLocale);
    std::cout << "C++ global locale set to hashLocale (name: \""
    << std::locale().name() << "\")" << std::endl;

    // Check current locales
    std::cout << "\nCurrent locales:" << std::endl;
    std::cout << "  C locale: " << std::setlocale(LC_ALL, nullptr) << std::endl;
    std::cout << "  C++ locale: " << std::locale().name() << std::endl;

    const double value = 23.9;

    // 4. double -> string conversion (uses C locale internally via snprintf)
    std::string numToStr = boost::lexical_cast < std::string > (value);
    std::cout << "\ndouble -> string: " << value << " -> \"" << numToStr << "\"" << std::endl;
    std::cout << "  Contains '.': " << (numToStr.find('.') != std::string::npos ? "yes" : "no") << std::endl;
    std::cout << "  Contains '#': " << (numToStr.find('#') != std::string::npos ? "yes" : "no") << std::endl;

    // 5. string -> double conversion (uses C++ locale via stringstream)
    // BUG: expects '#' (C++ locale) but string contains '.' (from C locale)
    std::cout << "\nstring -> double: trying to parse \"" << numToStr << "\"" << std::endl;
    try
    {
      double result = boost::lexical_cast<double>(numToStr);
      std::cout << "  Result: " << result << " (unexpected success)" << std::endl;
    }
    catch(const boost::bad_lexical_cast& e)
    {
      std::cout << "  BUG DEMONSTRATED: boost::bad_lexical_cast - " << e.what() << std::endl;
      std::cout << "  Reason: C locale formatted with '.', but C++ locale expects '#'" << std::endl;
    }

    // 6. Parsing with '#' works when using current C++ locale
    std::cout << "\nParsing \"1#5\" with current C++ global locale:" << std::endl;
    double hashResult = boost::lexical_cast<double>("1#5");
    std::cout << "  Result: " << hashResult << " (works - matches C++ locale)" << std::endl;

  }
  catch(const std::exception& e)
  {
    std::cerr << "Exception: " << e.what() << std::endl;
    return 1;
  }

  return 0;
}

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions