Skip to content

Commit 389b069

Browse files
committed
Add format_c, printf_c locale-independent formatting.
The default functions all are dependent on either the global native locale, or the locale associated with the stream they are outputting to. This can lead to 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. This patch adds format_c, printf_c, printfln_c, which force the classic "C" locale ('.' decimals, among other things). This incurs a performance penalty -- in particular, when being used on I/O streams it will force a flush when the locale for the stream is saved and restored -- but it is the safe thing to do for persistent, portable output.
1 parent 689695c commit 389b069

File tree

1 file changed

+96
-1
lines changed

1 file changed

+96
-1
lines changed

tinyformat.h

Lines changed: 96 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -955,14 +955,15 @@ inline void vformat(std::ostream& out, const char* fmt, FormatListRef list)
955955
#ifdef TINYFORMAT_USE_VARIADIC_TEMPLATES
956956

957957
/// Format list of arguments to the stream according to given format string.
958+
/// This honors the stream's existing locale conventions.
958959
template<typename... Args>
959960
void format(std::ostream& out, const char* fmt, const Args&... args)
960961
{
961962
vformat(out, fmt, makeFormatList(args...));
962963
}
963964

964965
/// Format list of arguments according to the given format string and return
965-
/// the result as a string.
966+
/// the result as a string, honoring the current global native locale.
966967
template<typename... Args>
967968
std::string format(const char* fmt, const Args&... args)
968969
{
@@ -972,6 +973,7 @@ std::string format(const char* fmt, const Args&... args)
972973
}
973974

974975
/// Format list of arguments to std::cout, according to the given format string
976+
/// This honors std::out's existing locale conventions.
975977
template<typename... Args>
976978
void printf(const char* fmt, const Args&... args)
977979
{
@@ -986,6 +988,44 @@ void printfln(const char* fmt, const Args&... args)
986988
}
987989

988990

991+
/// Formatting functions ending in _c force the classic "C" locale (i.e. '.'
992+
/// for decimal). They may be more expensive than the default functions,
993+
/// but they are useful when you MUST be locale-independent (like for
994+
/// persistent saved output when you always need identical formatting
995+
/// that should not vary, or be mis-parsed if written by a computer set
996+
/// up for one locale but read by a computer with a different locale).
997+
template<typename... Args>
998+
void format_c(std::ostream& out, const char* fmt, const Args&... args)
999+
{
1000+
// Force "C" locale but save the previous one asssociated with the stream
1001+
std::locale oldloc = out.imbue (std::locale::classic());
1002+
vformat(out, fmt, makeFormatList(args...));
1003+
out.imbue (oldloc); // restore the original locale
1004+
}
1005+
1006+
template<typename... Args>
1007+
std::string format_c(const char* fmt, const Args&... args)
1008+
{
1009+
std::ostringstream oss;
1010+
oss.imbue (std::locale::classic()); // force "C" locale with '.' decimal
1011+
format(oss, fmt, args...);
1012+
return oss.str();
1013+
}
1014+
1015+
template<typename... Args>
1016+
void printf_c(const char* fmt, const Args&... args)
1017+
{
1018+
format_c(std::cout, fmt, args...);
1019+
}
1020+
1021+
template<typename... Args>
1022+
void printfln_c(const char* fmt, const Args&... args)
1023+
{
1024+
format_c(std::cout, fmt, args...);
1025+
std::cout << '\n';
1026+
}
1027+
1028+
9891029
#else // C++98 version
9901030

9911031
inline void format(std::ostream& out, const char* fmt)
@@ -1011,6 +1051,31 @@ inline void printfln(const char* fmt)
10111051
std::cout << '\n';
10121052
}
10131053

1054+
inline void format_c(std::ostream& out, const char* fmt)
1055+
{
1056+
std::locale oldloc = out.imbue (std::locale::classic());
1057+
vformat(out, fmt, makeFormatList());
1058+
out.imbue (oldloc); // restore the original locale
1059+
}
1060+
1061+
inline std::string format_c(const char* fmt)
1062+
{
1063+
std::ostringstream oss;
1064+
format_c(oss, fmt);
1065+
return oss.str();
1066+
}
1067+
1068+
inline void printf_c(const char* fmt)
1069+
{
1070+
format_c(std::cout, fmt);
1071+
}
1072+
1073+
inline void printfln_c(const char* fmt)
1074+
{
1075+
format_c(std::cout, fmt);
1076+
std::cout << '\n';
1077+
}
1078+
10141079
#define TINYFORMAT_MAKE_FORMAT_FUNCS(n) \
10151080
\
10161081
template<TINYFORMAT_ARGTYPES(n)> \
@@ -1038,6 +1103,36 @@ void printfln(const char* fmt, TINYFORMAT_VARARGS(n)) \
10381103
{ \
10391104
format(std::cout, fmt, TINYFORMAT_PASSARGS(n)); \
10401105
std::cout << '\n'; \
1106+
} \
1107+
\
1108+
template<TINYFORMAT_ARGTYPES(n)> \
1109+
void format_c(std::ostream& out, const char* fmt, TINYFORMAT_VARARGS(n)) \
1110+
{ \
1111+
std::locale oldloc = out.imbue (std::locale::classic()); \
1112+
vformat(out, fmt, makeFormatList(TINYFORMAT_PASSARGS(n))); \
1113+
out.imbue (oldloc); \
1114+
} \
1115+
\
1116+
template<TINYFORMAT_ARGTYPES(n)> \
1117+
std::string format_c(const char* fmt, TINYFORMAT_VARARGS(n)) \
1118+
{ \
1119+
std::ostringstream oss; \
1120+
oss.imbue (std::locale::classic()); \
1121+
format(oss, fmt, TINYFORMAT_PASSARGS(n)); \
1122+
return oss.str(); \
1123+
} \
1124+
\
1125+
template<TINYFORMAT_ARGTYPES(n)> \
1126+
void printf_c(const char* fmt, TINYFORMAT_VARARGS(n)) \
1127+
{ \
1128+
format_c(std::cout, fmt, TINYFORMAT_PASSARGS(n)); \
1129+
} \
1130+
\
1131+
template<TINYFORMAT_ARGTYPES(n)> \
1132+
void printfln_c(const char* fmt, TINYFORMAT_VARARGS(n)) \
1133+
{ \
1134+
format_c(std::cout, fmt, TINYFORMAT_PASSARGS(n)); \
1135+
std::cout << '\n'; \
10411136
}
10421137

10431138
TINYFORMAT_FOREACH_ARGNUM(TINYFORMAT_MAKE_FORMAT_FUNCS)

0 commit comments

Comments
 (0)