Skip to content

Commit fc19591

Browse files
committed
Make tinyformat aware of locales.
Without all this, you will get situations where format("%g",123.45) might return "123,45" if it happens to be running on a machine with locale "fr_FR", for example. If that is written to an output file that is later read on a computer using a locale with '.' as the decimal mark, it could mis-parse as "123.0", leading to hilarious and tragic results. It seems like the safe thing is to force the string-returning varieties of tinyformat to use classic locale ('.' decimal), with an optional means for the odd users to purposely specify a different (or native) locale. It also seems that the best policy for the stream-appending varieties of format() is to use the local already associated with those streams (C++ lets each stream have its own locale). 1. format() to an existing stream (including cout) is now documented to honor the stream's existing locale (as it always did). 2. format() that returns a string now will always format the string using the classic "C" locale, in particular meaning that floating point numbers will use '.' as the decimal mark, regardless of the "native" locale set by the OS or process that may have an unexpected choice of decimal point. 3. Add a new variety of format() that returns a string, but which takes an explicit locale, for cases where you don't want the "C" locale (for example, when generating strings that will appear in a UI that you want localized properly). You can make this variety use the current locale by passing std::locale() as the first argument (the default constructor of std::locale just grabs the global process locale).
1 parent 689695c commit fc19591

File tree

1 file changed

+31
-1
lines changed

1 file changed

+31
-1
lines changed

tinyformat.h

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,7 @@ template<typename T>
266266
inline void formatTruncated(std::ostream& out, const T& value, int ntrunc)
267267
{
268268
std::ostringstream tmp;
269+
tmp.imbue (out.getloc());
269270
tmp << value;
270271
std::string result = tmp.str();
271272
out.write(result.c_str(), (std::min)(ntrunc, static_cast<int>(result.size())));
@@ -812,6 +813,7 @@ inline void formatImpl(std::ostream& out, const char* fmt,
812813
// it crudely by formatting into a temporary string stream and
813814
// munging the resulting string.
814815
std::ostringstream tmpStream;
816+
tmpStream.imbue (out.getloc());
815817
tmpStream.copyfmt(out);
816818
tmpStream.setf(std::ios::showpos);
817819
arg.format(tmpStream, fmt, fmtEnd, ntrunc);
@@ -943,6 +945,7 @@ TINYFORMAT_FOREACH_ARGNUM(TINYFORMAT_MAKE_MAKEFORMATLIST)
943945
#endif
944946

945947
/// Format list of arguments to the stream according to the given format string.
948+
/// This honors the stream's existing locale conventions.
946949
///
947950
/// The name vformat() is chosen for the semantic similarity to vprintf(): the
948951
/// list of format arguments is held in a single function argument.
@@ -955,23 +958,40 @@ inline void vformat(std::ostream& out, const char* fmt, FormatListRef list)
955958
#ifdef TINYFORMAT_USE_VARIADIC_TEMPLATES
956959

957960
/// Format list of arguments to the stream according to given format string.
961+
/// This honors the stream's existing locale conventions.
958962
template<typename... Args>
959963
void format(std::ostream& out, const char* fmt, const Args&... args)
960964
{
961965
vformat(out, fmt, makeFormatList(args...));
962966
}
963967

964968
/// Format list of arguments according to the given format string and return
965-
/// the result as a string.
969+
/// the result as a string, using classic "C" locale conventions (e.g.,
970+
/// using '.' as decimal mark).
966971
template<typename... Args>
967972
std::string format(const char* fmt, const Args&... args)
968973
{
969974
std::ostringstream oss;
975+
oss.imbue (std::locale::classic()); // force "C" locale with '.' decimal
976+
format(oss, fmt, args...);
977+
return oss.str();
978+
}
979+
980+
/// Format list of arguments according to the given format string and return
981+
/// the result as a string, using an explicit locale. Passing loc as a
982+
/// default-constructed std::locale will result in adhering to the current
983+
/// "native" locale set by the OS.
984+
template<typename... Args>
985+
std::string format(const std::locale& loc, const char* fmt, const Args&... args)
986+
{
987+
std::ostringstream oss;
988+
oss.imbue (loc);
970989
format(oss, fmt, args...);
971990
return oss.str();
972991
}
973992

974993
/// Format list of arguments to std::cout, according to the given format string
994+
/// This honors std::out's existing locale conventions.
975995
template<typename... Args>
976996
void printf(const char* fmt, const Args&... args)
977997
{
@@ -1023,6 +1043,16 @@ template<TINYFORMAT_ARGTYPES(n)> \
10231043
std::string format(const char* fmt, TINYFORMAT_VARARGS(n)) \
10241044
{ \
10251045
std::ostringstream oss; \
1046+
oss.imbue (std::locale::classic()); \
1047+
format(oss, fmt, TINYFORMAT_PASSARGS(n)); \
1048+
return oss.str(); \
1049+
} \
1050+
\
1051+
template<TINYFORMAT_ARGTYPES(n)> \
1052+
std::string format(const std::locale& loc, const char* fmt, TINYFORMAT_VARARGS(n)) \
1053+
{ \
1054+
std::ostringstream oss; \
1055+
oss.imbue (loc); \
10261056
format(oss, fmt, TINYFORMAT_PASSARGS(n)); \
10271057
return oss.str(); \
10281058
} \

0 commit comments

Comments
 (0)