Search code examples
c++locale

How to use correctly std::locale::global?


I am using a library which calls std::locale:global to save and restore C++ locale settings.

But std::locale::global set C++ locale settings AND C locale settings as explained in this nice user guide: https://stdcxx.apache.org/doc/stdlibug/24-3.html

This code shows the potential issue:

#include <clocale>
#include <iostream>

class C
{
public:
  void Open()
  {
    // Save current locale settings and set standard one
    currentLocale = std::locale::global(std::locale::classic());

    std::cout << "Open" << std::endl;
  }

  void Close()
  {
    // Restore the previous locale settings
    std::locale::global(currentLocale);

    std::cout << "Close" << std::endl;
  }

  std::locale currentLocale;
};

int main(int argc, char** argv)
{
  std::setlocale(LC_CTYPE, "");
  std::cout << "LC_CTYPE = " << std::setlocale(LC_CTYPE, nullptr) << std::endl;
  C myObject;
  myObject.Open();
  myObject.Close();
  std::cout << "LC_CTYPE = " << std::setlocale(LC_CTYPE, nullptr) << std::endl;
  return 0;
}

It prints:

LC_CTYPE = en_US.UTF-8
Open
Close
LC_CTYPE = C

But calling Open & Close should not modify any C locale setting and we should get:

LC_CTYPE = en_US.UTF-8
Open
Close
LC_CTYPE = en_US.UTF-8

For now I am only concerned by LC_CTYPE setting so I set C++ LC_CTYPE setting equal to C LC_CTYPE setting before calling Open&Close and the issue is fixed:

int main(int argc, char** argv)
{
  std::setlocale(LC_CTYPE, "");
  std::cout << "LC_CTYPE = " << std::setlocale(LC_CTYPE, nullptr) << std::endl;
  C myObject;

  // set c++ locale settings equal to C settings to keep C settings
  const std::string ctypeValue = std::setlocale(LC_CTYPE, nullptr);
  std::locale cppLocale;
  std::locale modifiedcppLocale(cppLocale, ctypeValue, std::locale::ctype);
  std::locale::global(modifiedcppLocale);

  myObject.Open();
  myObject.Close();
  std::cout << "LC_CTYPE = " << std::setlocale(LC_CTYPE, nullptr) << std::endl;
  return 0;
}

So I have general questions about std::locale::global:

1°) Why std::locale::global also set C locale settings ?

2°) Is it ok to store/restore locale settings by calling only std::locale::global ? It seems that we should also store/restore C locale settings by calling std::setlocale

3°) Is there a solution to have always C++ locale settings = C locale settings ?


Solution

  • So there is C++ std::locale and C locale. And they are separate. One is in C++. The other is in C. In C the locale is global. In C++ the locale is only really the default std::locale() constructor.

    And there are locale categories like LC_CTYPE. Then there is the whole locale LC_ALL. LC_ALL has all locale categories together combined into one.

    With std::setlocale(LC_CTYPE, ""); you are setting one LC_CTYPE C locale category.

    After setting std::setlocale(LC_CTYPE, "") the LC_ALL locale on glibc becomes LC_CTYPE=en_US.UTF-8;LC_NUMERIC=C;LC_TIME=C;LC_COLLATE=C;LC_MONETARY=C;LC_MESSAGES=C;LC_PAPER=C;LC_NAME=C;LC_ADDRESS=C;LC_TELEPHONE=C;LC_MEASUREMENT=C;LC_IDENTIFICATION=C, which you can do std::cout << std::setlocale(LC_ALL, nullptr) to check.

    With std::locale::global(std::locale::classic()) you are setting C++ locale and overriding C locale LC_ALL and you are getting the previous only C++ part of std::locale.

    So now you have overridden C locale and stored previous C++ locale. The previous C locale is lost.

    Why std::locale::global also set C locale settings ?

    Documentation https://en.cppreference.com/w/cpp/locale/locale/global states that, " If loc has a name, also replaces the C locale as if by std::setlocale(LC_ALL, loc.name().c_str());". LC_ALL is the whole locale at once, setting LC_ALL overrides all locale categories.

    Is it ok to store/restore locale settings by calling only std::locale::global ?

    Sure.

    It seems that we should also store/restore C locale settings by caling std::setlocale

    Yes.

    Is there a solution to have always C++ locale settings = C locale settings ?

    You can "sync" C++ default std::global global with C locale.

    int main(int argc, char** argv) {
      std::setlocale(LC_CTYPE, "");
      std::locale::global(std::locale(std::setlocale(LC_ALL, nullptr)));
    

    Overall, it feels odd to only setlocale(LC_CTYPE set single locale category. Typically, in C programs, main starts with setlocale(LC_ALL, "") set the locale (i.e. set all locale categories) to user defined locale.

    Typically, in C++, you would start main with setting C locale to user defined locale and setting C++ default std::locale locale to user defined locale, with:

    int main(int argc, char** argv) {
      std::locale::global(std::locale(""));