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;
}

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions