std::ostringstream & LC_NUMERIC

M

mathieu

Hi,

I am playing with the following C++ piece of code (*). At least on
my system debian/gcc 4.3 it looks like I am not writing out a floating
point separator as a comma. what are the operation affected by the
LC_NUMERIC env var value ?

Thanks
-Mathieu


(*)
#include <sstream>
#include <iostream>
#include <stdlib.h>

int main(int argc, char *argv[])
{
setenv("LC_NUMERIC", "fr_FR", 1);
std::eek:stringstream os;
double d = 1.2;
os << d;
std::string s = os.str();
std::cout << s << std::endl;
std::string::size_type pos_comma = s.find( "," );
if( pos_comma != std::string::npos )
{
return 1;
}
std::string::size_type pos_dot = s.find( "." );
if( pos_dot == std::string::npos )
{
return 1;
}
std::cout << "dot found" << std::endl;
return 0;
}
 
J

James Kanze

I am playing with the following C++ piece of code (*). At
least on my system debian/gcc 4.3 it looks like I am not
writing out a floating point separator as a comma. what are
the operation affected by the LC_NUMERIC env var value ?

In C++, nothing. All setting an envirionment variable does is
make it available to your process and its sub-processes (at
least under Unix---what it does outside of your process is
really very system dependent).

What you usually do in C++ is just the opposite; you read the
variable to know how to set your locale. While creating a
locale with the name "" (an empty string) is formally
implementation defined, it is a more or less established
convention, at least under Unix, that this locale will depend on
all of the LC_ environment variables. In other words, you will
use the locale the user specified.
#include <sstream>
#include <iostream>
#include <stdlib.h>
int main(int argc, char *argv[])
{
setenv("LC_NUMERIC", "fr_FR", 1);

This could affect creating a locale with the empty string, but
the main reason you would want to do this would be to inform
sub-processes to use the "fr_FR" locale.
std::eek:stringstream os;
double d = 1.2;
os << d;
std::string s = os.str();
std::cout << s << std::endl;
std::string::size_type pos_comma = s.find( "," );
if( pos_comma != std::string::npos )
{
return 1;
}
std::string::size_type pos_dot = s.find( "." );
if( pos_dot == std::string::npos )
{
return 1;
}
std::cout << "dot found" << std::endl;
return 0;
}

And you've never touched the actual locales used by your
program. In general:

-- A locale (i.e. std::locale) object created with the empty
string as a name will normally correspond to the "locale"
active in whatever process invoked you---under Unix, it will
normally use the LC_ environment variables, and perhaps
LANG, to determine this.

-- There is a global locale, used whenever you do not specify a
locale. On program start up, this is set to the "C" locale
(which might be appropriate for parsing C++ sources, but not
for much else); you can change it by calling
std::locale::global with the locale you want as the global
locale.

Combined with the previous point: almost every program which
generates human output should start with:
std::locale::global( std::locale( "" ) ) ;
Or if you really want to force a French locale, even for
users who don't want it:
std::locale::global( std::locale( "fr_FR" ) ) ;
(assuming the Unix naming conventions for locales.)

-- For any given IO, you can force the locale just for that
stream, by using imbue. Thus, if you've done the above (and
thus don't really know what the global locale is), but want
to output C++ code (for example), you should imbue the file
with the "C" locale; either:
output.imbue( std::locale( "C" ) ) ;
or:
output.imbue( std::locale::classic() ) ;
(which uses a special static member function to get this
very special locale). Similarly, if you want to force
output in the French locale for just this file:
output.imbue( std::locale( "fr_FR" ) ) ;

-- Finally, you can mix locales. If you want the classic
locale in general, but you want numbers formatted according
to the rules in French, you can create a custom locale for
this, e.g.
std::locale( std::locale::classic(),
"fr_FR",
std::locale::numeric )
In otherwords, copy std::locale::classic() (the first
argument), except for the numeric category (the third
argument, which can be an or'ed list of facets), which is
taken from the locale named "fr_FR".

In general, this is fairly tricky, however, and you have to
know what you are doing, and how the different "facets"
interact. (Each category is implemented by one or more
facets.)
 
M

mathieu

I am playing with the following C++ piece of code (*). At
least on my system debian/gcc 4.3 it looks like I am not
writing out a floating point separator as a comma. what are
the operation affected by the LC_NUMERIC env var value ?

In C++, nothing. All setting an envirionment variable does is
make it available to your process and its sub-processes (at
least under Unix---what it does outside of your process is
really very system dependent).

What you usually do in C++ is just the opposite; you read the
variable to know how to set your locale. While creating a
locale with the name "" (an empty string) is formally
implementation defined, it is a more or less established
convention, at least under Unix, that this locale will depend on
all of the LC_ environment variables. In other words, you will
use the locale the user specified.
#include <sstream>
#include <iostream>
#include <stdlib.h>
int main(int argc, char *argv[])
{
setenv("LC_NUMERIC", "fr_FR", 1);

This could affect creating a locale with the empty string, but
the main reason you would want to do this would be to inform
sub-processes to use the "fr_FR" locale.


std::eek:stringstream os;
double d = 1.2;
os << d;
std::string s = os.str();
std::cout << s << std::endl;
std::string::size_type pos_comma = s.find( "," );
if( pos_comma != std::string::npos )
{
return 1;
}
std::string::size_type pos_dot = s.find( "." );
if( pos_dot == std::string::npos )
{
return 1;
}
std::cout << "dot found" << std::endl;
return 0;
}

And you've never touched the actual locales used by your
program. In general:

-- A locale (i.e. std::locale) object created with the empty
string as a name will normally correspond to the "locale"
active in whatever process invoked you---under Unix, it will
normally use the LC_ environment variables, and perhaps
LANG, to determine this.

-- There is a global locale, used whenever you do not specify a
locale. On program start up, this is set to the "C" locale
(which might be appropriate for parsing C++ sources, but not
for much else); you can change it by calling
std::locale::global with the locale you want as the global
locale.

Combined with the previous point: almost every program which
generates human output should start with:
std::locale::global( std::locale( "" ) ) ;
Or if you really want to force a French locale, even for
users who don't want it:
std::locale::global( std::locale( "fr_FR" ) ) ;
(assuming the Unix naming conventions for locales.)

-- For any given IO, you can force the locale just for that
stream, by using imbue. Thus, if you've done the above (and
thus don't really know what the global locale is), but want
to output C++ code (for example), you should imbue the file
with the "C" locale; either:
output.imbue( std::locale( "C" ) ) ;
or:
output.imbue( std::locale::classic() ) ;
(which uses a special static member function to get this
very special locale). Similarly, if you want to force
output in the French locale for just this file:
output.imbue( std::locale( "fr_FR" ) ) ;

-- Finally, you can mix locales. If you want the classic
locale in general, but you want numbers formatted according
to the rules in French, you can create a custom locale for
this, e.g.
std::locale( std::locale::classic(),
"fr_FR",
std::locale::numeric )
In otherwords, copy std::locale::classic() (the first
argument), except for the numeric category (the third
argument, which can be an or'ed list of facets), which is
taken from the locale named "fr_FR".

In general, this is fairly tricky, however, and you have to
know what you are doing, and how the different "facets"
interact. (Each category is implemented by one or more
facets.)

Thanks ! That's extremely detailed :)

Since I am a just library author I can not rely on the fact that my
user will start their main program with
std::locale::global( std::locale( "" ) ) ;

Instead I'll have to make sure any o*stream are created (within the
lib, or externally by the user) with
os.imbue(std::locale::classic());

Regards
-Mathieu
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,968
Messages
2,570,149
Members
46,695
Latest member
StanleyDri

Latest Threads

Top