Commit 5dc2cc1a by Abseil Team Committed by Copybara-Service

Adds support for wchar_t/wchar_t*/std::wstring{_view} arguments to StrFormat().

This converts to UTF-8 regardless of locale.

PiperOrigin-RevId: 588186076
Change-Id: I2c9598279b413d460e13ad65da2ba421c0b40b83
parent 3e6ecec7
...@@ -1247,6 +1247,10 @@ cc_library( ...@@ -1247,6 +1247,10 @@ cc_library(
linkopts = ABSL_DEFAULT_LINKOPTS, linkopts = ABSL_DEFAULT_LINKOPTS,
deps = [ deps = [
":str_format_internal", ":str_format_internal",
":string_view",
"//absl/base:config",
"//absl/base:core_headers",
"//absl/types:span",
], ],
) )
...@@ -1277,6 +1281,7 @@ cc_library( ...@@ -1277,6 +1281,7 @@ cc_library(
":strings", ":strings",
"//absl/base:config", "//absl/base:config",
"//absl/base:core_headers", "//absl/base:core_headers",
"//absl/container:fixed_array",
"//absl/container:inlined_vector", "//absl/container:inlined_vector",
"//absl/functional:function_ref", "//absl/functional:function_ref",
"//absl/meta:type_traits", "//absl/meta:type_traits",
...@@ -1330,6 +1335,7 @@ cc_test( ...@@ -1330,6 +1335,7 @@ cc_test(
deps = [ deps = [
":str_format", ":str_format",
":str_format_internal", ":str_format_internal",
"//absl/base:config",
"@com_google_googletest//:gtest", "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main", "@com_google_googletest//:gtest_main",
], ],
...@@ -1366,12 +1372,16 @@ cc_test( ...@@ -1366,12 +1372,16 @@ cc_test(
copts = ABSL_TEST_COPTS, copts = ABSL_TEST_COPTS,
visibility = ["//visibility:private"], visibility = ["//visibility:private"],
deps = [ deps = [
":str_format",
":str_format_internal", ":str_format_internal",
":strings", ":strings",
"//absl/base:config",
"//absl/base:core_headers", "//absl/base:core_headers",
"//absl/base:raw_logging_internal", "//absl/base:raw_logging_internal",
"//absl/log", "//absl/log",
"//absl/numeric:int128",
"//absl/types:optional", "//absl/types:optional",
"//absl/types:span",
"@com_google_googletest//:gtest", "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main", "@com_google_googletest//:gtest_main",
], ],
...@@ -1397,6 +1407,8 @@ cc_test( ...@@ -1397,6 +1407,8 @@ cc_test(
visibility = ["//visibility:private"], visibility = ["//visibility:private"],
deps = [ deps = [
":str_format_internal", ":str_format_internal",
":string_view",
"//absl/base:config",
"//absl/base:core_headers", "//absl/base:core_headers",
"@com_google_googletest//:gtest", "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main", "@com_google_googletest//:gtest_main",
......
...@@ -470,7 +470,11 @@ absl_cc_library( ...@@ -470,7 +470,11 @@ absl_cc_library(
COPTS COPTS
${ABSL_DEFAULT_COPTS} ${ABSL_DEFAULT_COPTS}
DEPS DEPS
absl::config
absl::core_headers
absl::span
absl::str_format_internal absl::str_format_internal
absl::string_view
PUBLIC PUBLIC
) )
...@@ -501,6 +505,7 @@ absl_cc_library( ...@@ -501,6 +505,7 @@ absl_cc_library(
absl::strings absl::strings
absl::config absl::config
absl::core_headers absl::core_headers
absl::fixed_array
absl::inlined_vector absl::inlined_vector
absl::numeric_representation absl::numeric_representation
absl::type_traits absl::type_traits
...@@ -548,6 +553,7 @@ absl_cc_test( ...@@ -548,6 +553,7 @@ absl_cc_test(
COPTS COPTS
${ABSL_TEST_COPTS} ${ABSL_TEST_COPTS}
DEPS DEPS
absl::config
absl::str_format absl::str_format
absl::str_format_internal absl::str_format_internal
GTest::gmock_main GTest::gmock_main
...@@ -585,12 +591,15 @@ absl_cc_test( ...@@ -585,12 +591,15 @@ absl_cc_test(
COPTS COPTS
${ABSL_TEST_COPTS} ${ABSL_TEST_COPTS}
DEPS DEPS
absl::strings absl::config
absl::str_format_internal
absl::core_headers absl::core_headers
absl::int128
absl::log absl::log
absl::raw_logging_internal absl::raw_logging_internal
absl::int128 absl::span
absl::str_format
absl::str_format_internal
absl::strings
GTest::gmock_main GTest::gmock_main
) )
...@@ -616,6 +625,8 @@ absl_cc_test( ...@@ -616,6 +625,8 @@ absl_cc_test(
${ABSL_TEST_COPTS} ${ABSL_TEST_COPTS}
DEPS DEPS
absl::str_format_internal absl::str_format_internal
absl::string_view
absl::config
absl::core_headers absl::core_headers
GTest::gmock_main GTest::gmock_main
) )
......
...@@ -18,15 +18,28 @@ ...@@ -18,15 +18,28 @@
// //
#include "absl/strings/internal/str_format/arg.h" #include "absl/strings/internal/str_format/arg.h"
#include <algorithm>
#include <cassert> #include <cassert>
#include <cerrno> #include <cstddef>
#include <cstdint>
#include <cstdlib> #include <cstdlib>
#include <cstring>
#include <cwchar>
#include <string> #include <string>
#include <type_traits> #include <type_traits>
#include "absl/base/port.h" #include "absl/base/config.h"
#include "absl/base/optimization.h"
#include "absl/container/fixed_array.h"
#include "absl/numeric/int128.h"
#include "absl/strings/internal/str_format/extension.h"
#include "absl/strings/internal/str_format/float_conversion.h" #include "absl/strings/internal/str_format/float_conversion.h"
#include "absl/strings/numbers.h" #include "absl/strings/numbers.h"
#include "absl/strings/string_view.h"
#if defined(ABSL_HAVE_STD_STRING_VIEW)
#include <string_view>
#endif
namespace absl { namespace absl {
ABSL_NAMESPACE_BEGIN ABSL_NAMESPACE_BEGIN
...@@ -298,6 +311,83 @@ inline bool ConvertStringArg(string_view v, const FormatConversionSpecImpl conv, ...@@ -298,6 +311,83 @@ inline bool ConvertStringArg(string_view v, const FormatConversionSpecImpl conv,
conv.has_left_flag()); conv.has_left_flag());
} }
struct ShiftState {
bool saw_high_surrogate = false;
uint8_t bits = 0;
};
// Converts `v` from UTF-16 or UTF-32 to UTF-8 and writes to `buf`. `buf` is
// assumed to have enough space for the output. `s` is used to carry state
// between successive calls with a UTF-16 surrogate pair. Returns the number of
// chars written, or `static_cast<size_t>(-1)` on failure.
//
// This is basically std::wcrtomb(), but always outputting UTF-8 instead of
// respecting the current locale.
inline size_t WideToUtf8(wchar_t wc, char *buf, ShiftState &s) {
const auto v = static_cast<uint32_t>(wc);
if (v < 0x80) {
*buf = static_cast<char>(v);
return 1;
} else if (v < 0x800) {
*buf++ = static_cast<char>(0xc0 | (v >> 6));
*buf = static_cast<char>(0x80 | (v & 0x3f));
return 2;
} else if (v < 0xd800 || (v - 0xe000) < 0x2000) {
*buf++ = static_cast<char>(0xe0 | (v >> 12));
*buf++ = static_cast<char>(0x80 | ((v >> 6) & 0x3f));
*buf = static_cast<char>(0x80 | (v & 0x3f));
return 3;
} else if ((v - 0x10000) < 0x100000) {
*buf++ = static_cast<char>(0xf0 | (v >> 18));
*buf++ = static_cast<char>(0x80 | ((v >> 12) & 0x3f));
*buf++ = static_cast<char>(0x80 | ((v >> 6) & 0x3f));
*buf = static_cast<char>(0x80 | (v & 0x3f));
return 4;
} else if (v < 0xdc00) {
s.saw_high_surrogate = true;
s.bits = static_cast<uint8_t>(v & 0x3);
const uint8_t high_bits = ((v >> 6) & 0xf) + 1;
*buf++ = static_cast<char>(0xf0 | (high_bits >> 2));
*buf =
static_cast<char>(0x80 | static_cast<uint8_t>((high_bits & 0x3) << 4) |
static_cast<uint8_t>((v >> 2) & 0xf));
return 2;
} else if (v < 0xe000 && s.saw_high_surrogate) {
*buf++ = static_cast<char>(0x80 | static_cast<uint8_t>(s.bits << 4) |
static_cast<uint8_t>((v >> 6) & 0xf));
*buf = static_cast<char>(0x80 | (v & 0x3f));
s.saw_high_surrogate = false;
s.bits = 0;
return 2;
} else {
return static_cast<size_t>(-1);
}
}
inline bool ConvertStringArg(const wchar_t *v,
size_t len,
const FormatConversionSpecImpl conv,
FormatSinkImpl *sink) {
FixedArray<char> mb(len * 4);
ShiftState s;
size_t chars_written = 0;
for (size_t i = 0; i < len; ++i) {
const size_t chars = WideToUtf8(v[i], &mb[chars_written], s);
if (chars == static_cast<size_t>(-1)) { return false; }
chars_written += chars;
}
return ConvertStringArg(string_view(mb.data(), chars_written), conv, sink);
}
bool ConvertWCharTImpl(wchar_t v, const FormatConversionSpecImpl conv,
FormatSinkImpl *sink) {
char mb[4];
ShiftState s;
const size_t chars_written = WideToUtf8(v, mb, s);
return chars_written != static_cast<size_t>(-1) && !s.saw_high_surrogate &&
ConvertStringArg(string_view(mb, chars_written), conv, sink);
}
} // namespace } // namespace
bool ConvertBoolArg(bool v, FormatSinkImpl *sink) { bool ConvertBoolArg(bool v, FormatSinkImpl *sink) {
...@@ -316,11 +406,14 @@ bool ConvertIntArg(T v, FormatConversionSpecImpl conv, FormatSinkImpl *sink) { ...@@ -316,11 +406,14 @@ bool ConvertIntArg(T v, FormatConversionSpecImpl conv, FormatSinkImpl *sink) {
// This odd casting is due to a bug in -Wswitch behavior in gcc49 which causes // This odd casting is due to a bug in -Wswitch behavior in gcc49 which causes
// it to complain about a switch/case type mismatch, even though both are // it to complain about a switch/case type mismatch, even though both are
// FormatConverionChar. Likely this is because at this point // FormatConversionChar. Likely this is because at this point
// FormatConversionChar is declared, but not defined. // FormatConversionChar is declared, but not defined.
switch (static_cast<uint8_t>(conv.conversion_char())) { switch (static_cast<uint8_t>(conv.conversion_char())) {
case static_cast<uint8_t>(FormatConversionCharInternal::c): case static_cast<uint8_t>(FormatConversionCharInternal::c):
return ConvertCharImpl(static_cast<char>(v), conv, sink); return (std::is_same<T, wchar_t>::value ||
(conv.length_mod() == LengthMod::l))
? ConvertWCharTImpl(static_cast<wchar_t>(v), conv, sink)
: ConvertCharImpl(static_cast<char>(v), conv, sink);
case static_cast<uint8_t>(FormatConversionCharInternal::o): case static_cast<uint8_t>(FormatConversionCharInternal::o):
as_digits.PrintAsOct(static_cast<U>(v)); as_digits.PrintAsOct(static_cast<U>(v));
...@@ -372,6 +465,8 @@ template bool ConvertIntArg<signed char>(signed char v, ...@@ -372,6 +465,8 @@ template bool ConvertIntArg<signed char>(signed char v,
template bool ConvertIntArg<unsigned char>(unsigned char v, template bool ConvertIntArg<unsigned char>(unsigned char v,
FormatConversionSpecImpl conv, FormatConversionSpecImpl conv,
FormatSinkImpl *sink); FormatSinkImpl *sink);
template bool ConvertIntArg<wchar_t>(wchar_t v, FormatConversionSpecImpl conv,
FormatSinkImpl *sink);
template bool ConvertIntArg<short>(short v, // NOLINT template bool ConvertIntArg<short>(short v, // NOLINT
FormatConversionSpecImpl conv, FormatConversionSpecImpl conv,
FormatSinkImpl *sink); FormatSinkImpl *sink);
...@@ -403,16 +498,29 @@ StringConvertResult FormatConvertImpl(const std::string &v, ...@@ -403,16 +498,29 @@ StringConvertResult FormatConvertImpl(const std::string &v,
return {ConvertStringArg(v, conv, sink)}; return {ConvertStringArg(v, conv, sink)};
} }
StringConvertResult FormatConvertImpl(const std::wstring &v,
const FormatConversionSpecImpl conv,
FormatSinkImpl *sink) {
return {ConvertStringArg(v.data(), v.size(), conv, sink)};
}
StringConvertResult FormatConvertImpl(string_view v, StringConvertResult FormatConvertImpl(string_view v,
const FormatConversionSpecImpl conv, const FormatConversionSpecImpl conv,
FormatSinkImpl *sink) { FormatSinkImpl *sink) {
return {ConvertStringArg(v, conv, sink)}; return {ConvertStringArg(v, conv, sink)};
} }
ArgConvertResult<FormatConversionCharSetUnion( #if defined(ABSL_HAVE_STD_STRING_VIEW)
FormatConversionCharSetInternal::s, FormatConversionCharSetInternal::p)> StringConvertResult FormatConvertImpl(std::wstring_view v,
FormatConvertImpl(const char *v, const FormatConversionSpecImpl conv, const FormatConversionSpecImpl conv,
FormatSinkImpl *sink) { FormatSinkImpl* sink) {
return {ConvertStringArg(v.data(), v.size(), conv, sink)};
}
#endif
StringPtrConvertResult FormatConvertImpl(const char* v,
const FormatConversionSpecImpl conv,
FormatSinkImpl* sink) {
if (conv.conversion_char() == FormatConversionCharInternal::p) if (conv.conversion_char() == FormatConversionCharInternal::p)
return {FormatConvertImpl(VoidPtr(v), conv, sink).value}; return {FormatConvertImpl(VoidPtr(v), conv, sink).value};
size_t len; size_t len;
...@@ -427,6 +535,30 @@ FormatConvertImpl(const char *v, const FormatConversionSpecImpl conv, ...@@ -427,6 +535,30 @@ FormatConvertImpl(const char *v, const FormatConversionSpecImpl conv,
return {ConvertStringArg(string_view(v, len), conv, sink)}; return {ConvertStringArg(string_view(v, len), conv, sink)};
} }
StringPtrConvertResult FormatConvertImpl(const wchar_t* v,
const FormatConversionSpecImpl conv,
FormatSinkImpl* sink) {
if (conv.conversion_char() == FormatConversionCharInternal::p) {
return {FormatConvertImpl(VoidPtr(v), conv, sink).value};
}
size_t len;
if (v == nullptr) {
len = 0;
} else if (conv.precision() < 0) {
len = std::wcslen(v);
} else {
// If precision is set, we look for the NUL-terminator on the valid range.
len = static_cast<size_t>(std::find(v, v + conv.precision(), L'\0') - v);
}
return {ConvertStringArg(v, len, conv, sink)};
}
StringPtrConvertResult FormatConvertImpl(std::nullptr_t,
const FormatConversionSpecImpl conv,
FormatSinkImpl* sink) {
return FormatConvertImpl(static_cast<const char*>(nullptr), conv, sink);
}
// ==================== Raw pointers ==================== // ==================== Raw pointers ====================
ArgConvertResult<FormatConversionCharSetInternal::p> FormatConvertImpl( ArgConvertResult<FormatConversionCharSetInternal::p> FormatConvertImpl(
VoidPtr v, const FormatConversionSpecImpl conv, FormatSinkImpl *sink) { VoidPtr v, const FormatConversionSpecImpl conv, FormatSinkImpl *sink) {
...@@ -461,6 +593,11 @@ CharConvertResult FormatConvertImpl(char v, const FormatConversionSpecImpl conv, ...@@ -461,6 +593,11 @@ CharConvertResult FormatConvertImpl(char v, const FormatConversionSpecImpl conv,
FormatSinkImpl *sink) { FormatSinkImpl *sink) {
return {ConvertIntArg(v, conv, sink)}; return {ConvertIntArg(v, conv, sink)};
} }
CharConvertResult FormatConvertImpl(wchar_t v,
const FormatConversionSpecImpl conv,
FormatSinkImpl* sink) {
return {ConvertIntArg(v, conv, sink)};
}
// ==================== Ints ==================== // ==================== Ints ====================
IntegralConvertResult FormatConvertImpl(signed char v, IntegralConvertResult FormatConvertImpl(signed char v,
......
...@@ -19,8 +19,9 @@ ...@@ -19,8 +19,9 @@
#include <wchar.h> #include <wchar.h>
#include <algorithm> #include <algorithm>
#include <cstddef>
#include <cstdint>
#include <cstdio> #include <cstdio>
#include <iomanip>
#include <limits> #include <limits>
#include <memory> #include <memory>
#include <sstream> #include <sstream>
...@@ -28,13 +29,18 @@ ...@@ -28,13 +29,18 @@
#include <type_traits> #include <type_traits>
#include <utility> #include <utility>
#include "absl/base/port.h" #include "absl/base/config.h"
#include "absl/base/optimization.h"
#include "absl/meta/type_traits.h" #include "absl/meta/type_traits.h"
#include "absl/numeric/int128.h" #include "absl/numeric/int128.h"
#include "absl/strings/has_absl_stringify.h" #include "absl/strings/has_absl_stringify.h"
#include "absl/strings/internal/str_format/extension.h" #include "absl/strings/internal/str_format/extension.h"
#include "absl/strings/string_view.h" #include "absl/strings/string_view.h"
#if defined(ABSL_HAVE_STD_STRING_VIEW)
#include <string_view>
#endif
namespace absl { namespace absl {
ABSL_NAMESPACE_BEGIN ABSL_NAMESPACE_BEGIN
...@@ -97,6 +103,9 @@ extern template bool ConvertIntArg<signed char>(signed char v, ...@@ -97,6 +103,9 @@ extern template bool ConvertIntArg<signed char>(signed char v,
extern template bool ConvertIntArg<unsigned char>(unsigned char v, extern template bool ConvertIntArg<unsigned char>(unsigned char v,
FormatConversionSpecImpl conv, FormatConversionSpecImpl conv,
FormatSinkImpl* sink); FormatSinkImpl* sink);
extern template bool ConvertIntArg<wchar_t>(wchar_t v,
FormatConversionSpecImpl conv,
FormatSinkImpl* sink);
extern template bool ConvertIntArg<short>(short v, // NOLINT extern template bool ConvertIntArg<short>(short v, // NOLINT
FormatConversionSpecImpl conv, FormatConversionSpecImpl conv,
FormatSinkImpl* sink); FormatSinkImpl* sink);
...@@ -203,30 +212,49 @@ constexpr FormatConversionCharSet ExtractCharSet(ArgConvertResult<C>) { ...@@ -203,30 +212,49 @@ constexpr FormatConversionCharSet ExtractCharSet(ArgConvertResult<C>) {
return C; return C;
} }
using StringConvertResult = ArgConvertResult<FormatConversionCharSetUnion(
FormatConversionCharSetInternal::s, FormatConversionCharSetInternal::v)>;
ArgConvertResult<FormatConversionCharSetInternal::p> FormatConvertImpl( ArgConvertResult<FormatConversionCharSetInternal::p> FormatConvertImpl(
VoidPtr v, FormatConversionSpecImpl conv, FormatSinkImpl* sink); VoidPtr v, FormatConversionSpecImpl conv, FormatSinkImpl* sink);
// Strings. // Strings.
using StringConvertResult = ArgConvertResult<FormatConversionCharSetUnion(
FormatConversionCharSetInternal::s,
FormatConversionCharSetInternal::v)>;
StringConvertResult FormatConvertImpl(const std::string& v, StringConvertResult FormatConvertImpl(const std::string& v,
FormatConversionSpecImpl conv, FormatConversionSpecImpl conv,
FormatSinkImpl* sink); FormatSinkImpl* sink);
StringConvertResult FormatConvertImpl(const std::wstring& v,
FormatConversionSpecImpl conv,
FormatSinkImpl* sink);
StringConvertResult FormatConvertImpl(string_view v, StringConvertResult FormatConvertImpl(string_view v,
FormatConversionSpecImpl conv, FormatConversionSpecImpl conv,
FormatSinkImpl* sink); FormatSinkImpl* sink);
#if defined(ABSL_HAVE_STD_STRING_VIEW) && !defined(ABSL_USES_STD_STRING_VIEW) #if defined(ABSL_HAVE_STD_STRING_VIEW)
StringConvertResult FormatConvertImpl(std::wstring_view v,
FormatConversionSpecImpl conv,
FormatSinkImpl* sink);
#if !defined(ABSL_USES_STD_STRING_VIEW)
inline StringConvertResult FormatConvertImpl(std::string_view v, inline StringConvertResult FormatConvertImpl(std::string_view v,
FormatConversionSpecImpl conv, FormatConversionSpecImpl conv,
FormatSinkImpl* sink) { FormatSinkImpl* sink) {
return FormatConvertImpl(absl::string_view(v.data(), v.size()), conv, sink); return FormatConvertImpl(absl::string_view(v.data(), v.size()), conv, sink);
} }
#endif // ABSL_HAVE_STD_STRING_VIEW && !ABSL_USES_STD_STRING_VIEW #endif // !ABSL_USES_STD_STRING_VIEW
#endif // ABSL_HAVE_STD_STRING_VIEW
ArgConvertResult<FormatConversionCharSetUnion(
FormatConversionCharSetInternal::s, FormatConversionCharSetInternal::p)> using StringPtrConvertResult = ArgConvertResult<FormatConversionCharSetUnion(
FormatConvertImpl(const char* v, const FormatConversionSpecImpl conv, FormatConversionCharSetInternal::s,
FormatSinkImpl* sink); FormatConversionCharSetInternal::p)>;
StringPtrConvertResult FormatConvertImpl(const char* v,
FormatConversionSpecImpl conv,
FormatSinkImpl* sink);
StringPtrConvertResult FormatConvertImpl(const wchar_t* v,
FormatConversionSpecImpl conv,
FormatSinkImpl* sink);
// This overload is needed to disambiguate, since `nullptr` could match either
// of the other overloads equally well.
StringPtrConvertResult FormatConvertImpl(std::nullptr_t,
FormatConversionSpecImpl conv,
FormatSinkImpl* sink);
template <class AbslCord, typename std::enable_if<std::is_same< template <class AbslCord, typename std::enable_if<std::is_same<
AbslCord, absl::Cord>::value>::type* = nullptr> AbslCord, absl::Cord>::value>::type* = nullptr>
...@@ -280,6 +308,9 @@ FloatingConvertResult FormatConvertImpl(long double v, ...@@ -280,6 +308,9 @@ FloatingConvertResult FormatConvertImpl(long double v,
// Chars. // Chars.
CharConvertResult FormatConvertImpl(char v, FormatConversionSpecImpl conv, CharConvertResult FormatConvertImpl(char v, FormatConversionSpecImpl conv,
FormatSinkImpl* sink); FormatSinkImpl* sink);
CharConvertResult FormatConvertImpl(wchar_t v,
FormatConversionSpecImpl conv,
FormatSinkImpl* sink);
// Ints. // Ints.
IntegralConvertResult FormatConvertImpl(signed char v, IntegralConvertResult FormatConvertImpl(signed char v,
...@@ -441,6 +472,7 @@ class FormatArgImpl { ...@@ -441,6 +472,7 @@ class FormatArgImpl {
// Anything with a user-defined Convert will get its own vtable. // Anything with a user-defined Convert will get its own vtable.
// For everything else: // For everything else:
// - Decay char* and char arrays into `const char*` // - Decay char* and char arrays into `const char*`
// - Decay wchar_t* and wchar_t arrays into `const wchar_t*`
// - Decay any other pointer to `const void*` // - Decay any other pointer to `const void*`
// - Decay all enums to the integral promotion of their underlying type. // - Decay all enums to the integral promotion of their underlying type.
// - Decay function pointers to void*. // - Decay function pointers to void*.
...@@ -452,9 +484,13 @@ class FormatArgImpl { ...@@ -452,9 +484,13 @@ class FormatArgImpl {
using type = typename std::conditional< using type = typename std::conditional<
!kHasUserDefined && std::is_convertible<T, const char*>::value, !kHasUserDefined && std::is_convertible<T, const char*>::value,
const char*, const char*,
typename std::conditional<!kHasUserDefined && typename std::conditional<
std::is_convertible<T, VoidPtr>::value, !kHasUserDefined && std::is_convertible<T, const wchar_t*>::value,
VoidPtr, const T&>::type>::type; const wchar_t*,
typename std::conditional<
!kHasUserDefined && std::is_convertible<T, VoidPtr>::value,
VoidPtr,
const T&>::type>::type>::type;
}; };
template <typename T> template <typename T>
struct DecayType< struct DecayType<
...@@ -585,7 +621,7 @@ class FormatArgImpl { ...@@ -585,7 +621,7 @@ class FormatArgImpl {
E template bool FormatArgImpl::Dispatch<T>(Data, FormatConversionSpecImpl, \ E template bool FormatArgImpl::Dispatch<T>(Data, FormatConversionSpecImpl, \
void*) void*)
#define ABSL_INTERNAL_FORMAT_DISPATCH_OVERLOADS_EXPAND_(...) \ #define ABSL_INTERNAL_FORMAT_DISPATCH_OVERLOADS_EXPAND_NO_WSTRING_VIEW_(...) \
ABSL_INTERNAL_FORMAT_DISPATCH_INSTANTIATE_(str_format_internal::VoidPtr, \ ABSL_INTERNAL_FORMAT_DISPATCH_INSTANTIATE_(str_format_internal::VoidPtr, \
__VA_ARGS__); \ __VA_ARGS__); \
ABSL_INTERNAL_FORMAT_DISPATCH_INSTANTIATE_(bool, __VA_ARGS__); \ ABSL_INTERNAL_FORMAT_DISPATCH_INSTANTIATE_(bool, __VA_ARGS__); \
...@@ -611,7 +647,19 @@ class FormatArgImpl { ...@@ -611,7 +647,19 @@ class FormatArgImpl {
ABSL_INTERNAL_FORMAT_DISPATCH_INSTANTIATE_(long double, __VA_ARGS__); \ ABSL_INTERNAL_FORMAT_DISPATCH_INSTANTIATE_(long double, __VA_ARGS__); \
ABSL_INTERNAL_FORMAT_DISPATCH_INSTANTIATE_(const char*, __VA_ARGS__); \ ABSL_INTERNAL_FORMAT_DISPATCH_INSTANTIATE_(const char*, __VA_ARGS__); \
ABSL_INTERNAL_FORMAT_DISPATCH_INSTANTIATE_(std::string, __VA_ARGS__); \ ABSL_INTERNAL_FORMAT_DISPATCH_INSTANTIATE_(std::string, __VA_ARGS__); \
ABSL_INTERNAL_FORMAT_DISPATCH_INSTANTIATE_(string_view, __VA_ARGS__) ABSL_INTERNAL_FORMAT_DISPATCH_INSTANTIATE_(string_view, __VA_ARGS__); \
ABSL_INTERNAL_FORMAT_DISPATCH_INSTANTIATE_(const wchar_t*, __VA_ARGS__); \
ABSL_INTERNAL_FORMAT_DISPATCH_INSTANTIATE_(std::wstring, __VA_ARGS__)
#if defined(ABSL_HAVE_STD_STRING_VIEW)
#define ABSL_INTERNAL_FORMAT_DISPATCH_OVERLOADS_EXPAND_(...) \
ABSL_INTERNAL_FORMAT_DISPATCH_OVERLOADS_EXPAND_NO_WSTRING_VIEW_( \
__VA_ARGS__); \
ABSL_INTERNAL_FORMAT_DISPATCH_INSTANTIATE_(std::wstring_view, __VA_ARGS__)
#else
#define ABSL_INTERNAL_FORMAT_DISPATCH_OVERLOADS_EXPAND_(...) \
ABSL_INTERNAL_FORMAT_DISPATCH_OVERLOADS_EXPAND_NO_WSTRING_VIEW_(__VA_ARGS__)
#endif
ABSL_INTERNAL_FORMAT_DISPATCH_OVERLOADS_EXPAND_(extern); ABSL_INTERNAL_FORMAT_DISPATCH_OVERLOADS_EXPAND_(extern);
......
...@@ -14,9 +14,10 @@ ...@@ -14,9 +14,10 @@
#include "absl/strings/internal/str_format/arg.h" #include "absl/strings/internal/str_format/arg.h"
#include <ostream> #include <limits>
#include <string> #include <string>
#include "gtest/gtest.h" #include "gtest/gtest.h"
#include "absl/base/config.h"
#include "absl/strings/str_format.h" #include "absl/strings/str_format.h"
namespace absl { namespace absl {
...@@ -93,6 +94,21 @@ TEST_F(FormatArgImplTest, CharArraysDecayToCharPtr) { ...@@ -93,6 +94,21 @@ TEST_F(FormatArgImplTest, CharArraysDecayToCharPtr) {
FormatArgImplFriend::GetVTablePtrForTest(FormatArgImpl(kMyArray))); FormatArgImplFriend::GetVTablePtrForTest(FormatArgImpl(kMyArray)));
} }
extern const wchar_t kMyWCharTArray[];
TEST_F(FormatArgImplTest, WCharTArraysDecayToWCharTPtr) {
const wchar_t* a = L"";
EXPECT_EQ(FormatArgImplFriend::GetVTablePtrForTest(FormatArgImpl(a)),
FormatArgImplFriend::GetVTablePtrForTest(FormatArgImpl(L"")));
EXPECT_EQ(FormatArgImplFriend::GetVTablePtrForTest(FormatArgImpl(a)),
FormatArgImplFriend::GetVTablePtrForTest(FormatArgImpl(L"A")));
EXPECT_EQ(FormatArgImplFriend::GetVTablePtrForTest(FormatArgImpl(a)),
FormatArgImplFriend::GetVTablePtrForTest(FormatArgImpl(L"ABC")));
EXPECT_EQ(
FormatArgImplFriend::GetVTablePtrForTest(FormatArgImpl(a)),
FormatArgImplFriend::GetVTablePtrForTest(FormatArgImpl(kMyWCharTArray)));
}
TEST_F(FormatArgImplTest, OtherPtrDecayToVoidPtr) { TEST_F(FormatArgImplTest, OtherPtrDecayToVoidPtr) {
auto expected = FormatArgImplFriend::GetVTablePtrForTest( auto expected = FormatArgImplFriend::GetVTablePtrForTest(
FormatArgImpl(static_cast<void *>(nullptr))); FormatArgImpl(static_cast<void *>(nullptr)));
...@@ -124,6 +140,22 @@ TEST_F(FormatArgImplTest, WorksWithCharArraysOfUnknownSize) { ...@@ -124,6 +140,22 @@ TEST_F(FormatArgImplTest, WorksWithCharArraysOfUnknownSize) {
} }
const char kMyArray[] = "ABCDE"; const char kMyArray[] = "ABCDE";
TEST_F(FormatArgImplTest, WorksWithWCharTArraysOfUnknownSize) {
std::string s;
FormatSinkImpl sink(&s);
FormatConversionSpecImpl conv;
FormatConversionSpecImplFriend::SetConversionChar(
FormatConversionCharInternal::s, &conv);
FormatConversionSpecImplFriend::SetFlags(Flags(), &conv);
FormatConversionSpecImplFriend::SetWidth(-1, &conv);
FormatConversionSpecImplFriend::SetPrecision(-1, &conv);
EXPECT_TRUE(
FormatArgImplFriend::Convert(FormatArgImpl(kMyWCharTArray), conv, &sink));
sink.Flush();
EXPECT_EQ("ABCDE", s);
}
const wchar_t kMyWCharTArray[] = L"ABCDE";
} // namespace } // namespace
} // namespace str_format_internal } // namespace str_format_internal
ABSL_NAMESPACE_END ABSL_NAMESPACE_END
......
...@@ -14,10 +14,24 @@ ...@@ -14,10 +14,24 @@
#include "absl/strings/internal/str_format/bind.h" #include "absl/strings/internal/str_format/bind.h"
#include <algorithm>
#include <cassert>
#include <cerrno> #include <cerrno>
#include <cstddef>
#include <cstdio>
#include <ios>
#include <limits> #include <limits>
#include <ostream>
#include <sstream> #include <sstream>
#include <string> #include <string>
#include "absl/base/config.h"
#include "absl/base/optimization.h"
#include "absl/strings/internal/str_format/arg.h"
#include "absl/strings/internal/str_format/constexpr_parser.h"
#include "absl/strings/internal/str_format/extension.h"
#include "absl/strings/internal/str_format/output.h"
#include "absl/strings/string_view.h"
#include "absl/types/span.h"
namespace absl { namespace absl {
ABSL_NAMESPACE_BEGIN ABSL_NAMESPACE_BEGIN
...@@ -90,6 +104,8 @@ inline bool ArgContext::Bind(const UnboundConversion* unbound, ...@@ -90,6 +104,8 @@ inline bool ArgContext::Bind(const UnboundConversion* unbound,
} else { } else {
FormatConversionSpecImplFriend::SetFlags(unbound->flags, bound); FormatConversionSpecImplFriend::SetFlags(unbound->flags, bound);
} }
FormatConversionSpecImplFriend::SetLengthMod(unbound->length_mod, bound);
} else { } else {
FormatConversionSpecImplFriend::SetFlags(unbound->flags, bound); FormatConversionSpecImplFriend::SetFlags(unbound->flags, bound);
FormatConversionSpecImplFriend::SetWidth(-1, bound); FormatConversionSpecImplFriend::SetWidth(-1, bound);
...@@ -215,7 +231,7 @@ std::string& AppendPack(std::string* out, const UntypedFormatSpecImpl format, ...@@ -215,7 +231,7 @@ std::string& AppendPack(std::string* out, const UntypedFormatSpecImpl format,
return *out; return *out;
} }
std::string FormatPack(const UntypedFormatSpecImpl format, std::string FormatPack(UntypedFormatSpecImpl format,
absl::Span<const FormatArgImpl> args) { absl::Span<const FormatArgImpl> args) {
std::string out; std::string out;
if (ABSL_PREDICT_FALSE(!FormatUntyped(&out, format, args))) { if (ABSL_PREDICT_FALSE(!FormatUntyped(&out, format, args))) {
......
...@@ -15,16 +15,19 @@ ...@@ -15,16 +15,19 @@
#ifndef ABSL_STRINGS_INTERNAL_STR_FORMAT_BIND_H_ #ifndef ABSL_STRINGS_INTERNAL_STR_FORMAT_BIND_H_
#define ABSL_STRINGS_INTERNAL_STR_FORMAT_BIND_H_ #define ABSL_STRINGS_INTERNAL_STR_FORMAT_BIND_H_
#include <array> #include <cassert>
#include <cstdio> #include <cstdio>
#include <sstream> #include <ostream>
#include <string> #include <string>
#include "absl/base/port.h" #include "absl/base/config.h"
#include "absl/container/inlined_vector.h" #include "absl/container/inlined_vector.h"
#include "absl/strings/internal/str_format/arg.h" #include "absl/strings/internal/str_format/arg.h"
#include "absl/strings/internal/str_format/checker.h" #include "absl/strings/internal/str_format/checker.h"
#include "absl/strings/internal/str_format/constexpr_parser.h"
#include "absl/strings/internal/str_format/extension.h"
#include "absl/strings/internal/str_format/parser.h" #include "absl/strings/internal/str_format/parser.h"
#include "absl/strings/string_view.h"
#include "absl/types/span.h" #include "absl/types/span.h"
#include "absl/utility/utility.h" #include "absl/utility/utility.h"
...@@ -203,7 +206,7 @@ bool FormatUntyped(FormatRawSinkImpl raw_sink, UntypedFormatSpecImpl format, ...@@ -203,7 +206,7 @@ bool FormatUntyped(FormatRawSinkImpl raw_sink, UntypedFormatSpecImpl format,
std::string& AppendPack(std::string* out, UntypedFormatSpecImpl format, std::string& AppendPack(std::string* out, UntypedFormatSpecImpl format,
absl::Span<const FormatArgImpl> args); absl::Span<const FormatArgImpl> args);
std::string FormatPack(const UntypedFormatSpecImpl format, std::string FormatPack(UntypedFormatSpecImpl format,
absl::Span<const FormatArgImpl> args); absl::Span<const FormatArgImpl> args);
int FprintF(std::FILE* output, UntypedFormatSpecImpl format, int FprintF(std::FILE* output, UntypedFormatSpecImpl format,
......
...@@ -17,17 +17,18 @@ ...@@ -17,17 +17,18 @@
#include <cassert> #include <cassert>
#include <cstdint> #include <cstdint>
#include <cstdio>
#include <limits> #include <limits>
#include "absl/base/config.h"
#include "absl/base/const_init.h" #include "absl/base/const_init.h"
#include "absl/base/optimization.h"
#include "absl/strings/internal/str_format/extension.h" #include "absl/strings/internal/str_format/extension.h"
namespace absl { namespace absl {
ABSL_NAMESPACE_BEGIN ABSL_NAMESPACE_BEGIN
namespace str_format_internal { namespace str_format_internal {
enum class LengthMod : std::uint8_t { h, hh, l, ll, L, j, z, t, q, none };
// The analyzed properties of a single specified conversion. // The analyzed properties of a single specified conversion.
struct UnboundConversion { struct UnboundConversion {
// This is a user defined default constructor on purpose to skip the // This is a user defined default constructor on purpose to skip the
...@@ -306,7 +307,6 @@ constexpr const char* ConsumeConversion(const char* pos, const char* const end, ...@@ -306,7 +307,6 @@ constexpr const char* ConsumeConversion(const char* pos, const char* const end,
if (ABSL_PREDICT_FALSE(!tag.is_length())) return nullptr; if (ABSL_PREDICT_FALSE(!tag.is_length())) return nullptr;
// It is a length modifier. // It is a length modifier.
using str_format_internal::LengthMod;
LengthMod length_mod = tag.as_length(); LengthMod length_mod = tag.as_length();
ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR(); ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR();
if (c == 'h' && length_mod == LengthMod::h) { if (c == 'h' && length_mod == LengthMod::h) {
...@@ -322,6 +322,11 @@ constexpr const char* ConsumeConversion(const char* pos, const char* const end, ...@@ -322,6 +322,11 @@ constexpr const char* ConsumeConversion(const char* pos, const char* const end,
if (ABSL_PREDICT_FALSE(c == 'v')) return nullptr; if (ABSL_PREDICT_FALSE(c == 'v')) return nullptr;
if (ABSL_PREDICT_FALSE(!tag.is_conv())) return nullptr; if (ABSL_PREDICT_FALSE(!tag.is_conv())) return nullptr;
// `wchar_t` args are marked non-basic so `Bind()` will copy the length mod.
if (conv->length_mod == LengthMod::l && c == 'c') {
conv->flags = conv->flags | Flags::kNonBasic;
}
} }
#undef ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR #undef ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR
......
...@@ -16,16 +16,14 @@ ...@@ -16,16 +16,14 @@
#ifndef ABSL_STRINGS_INTERNAL_STR_FORMAT_EXTENSION_H_ #ifndef ABSL_STRINGS_INTERNAL_STR_FORMAT_EXTENSION_H_
#define ABSL_STRINGS_INTERNAL_STR_FORMAT_EXTENSION_H_ #define ABSL_STRINGS_INTERNAL_STR_FORMAT_EXTENSION_H_
#include <limits.h>
#include <cstddef> #include <cstddef>
#include <cstdint> #include <cstdint>
#include <cstring> #include <cstring>
#include <ostream> #include <ostream>
#include <string>
#include "absl/base/config.h" #include "absl/base/config.h"
#include "absl/base/port.h"
#include "absl/meta/type_traits.h"
#include "absl/strings/internal/str_format/output.h" #include "absl/strings/internal/str_format/output.h"
#include "absl/strings/string_view.h" #include "absl/strings/string_view.h"
...@@ -34,6 +32,7 @@ ABSL_NAMESPACE_BEGIN ...@@ -34,6 +32,7 @@ ABSL_NAMESPACE_BEGIN
enum class FormatConversionChar : uint8_t; enum class FormatConversionChar : uint8_t;
enum class FormatConversionCharSet : uint64_t; enum class FormatConversionCharSet : uint64_t;
enum class LengthMod : std::uint8_t { h, hh, l, ll, L, j, z, t, q, none };
namespace str_format_internal { namespace str_format_internal {
...@@ -139,7 +138,8 @@ enum class Flags : uint8_t { ...@@ -139,7 +138,8 @@ enum class Flags : uint8_t {
kAlt = 1 << 3, kAlt = 1 << 3,
kZero = 1 << 4, kZero = 1 << 4,
// This is not a real flag. It just exists to turn off kBasic when no other // This is not a real flag. It just exists to turn off kBasic when no other
// flags are set. This is for when width/precision are specified. // flags are set. This is for when width/precision are specified, or a length
// modifier affects the behavior ("%lc").
kNonBasic = 1 << 5, kNonBasic = 1 << 5,
}; };
...@@ -285,6 +285,8 @@ class FormatConversionSpecImpl { ...@@ -285,6 +285,8 @@ class FormatConversionSpecImpl {
bool has_alt_flag() const { return FlagsContains(flags_, Flags::kAlt); } bool has_alt_flag() const { return FlagsContains(flags_, Flags::kAlt); }
bool has_zero_flag() const { return FlagsContains(flags_, Flags::kZero); } bool has_zero_flag() const { return FlagsContains(flags_, Flags::kZero); }
LengthMod length_mod() const { return length_mod_; }
FormatConversionChar conversion_char() const { FormatConversionChar conversion_char() const {
// Keep this field first in the struct . It generates better code when // Keep this field first in the struct . It generates better code when
// accessing it when ConversionSpec is passed by value in registers. // accessing it when ConversionSpec is passed by value in registers.
...@@ -310,6 +312,7 @@ class FormatConversionSpecImpl { ...@@ -310,6 +312,7 @@ class FormatConversionSpecImpl {
friend struct str_format_internal::FormatConversionSpecImplFriend; friend struct str_format_internal::FormatConversionSpecImplFriend;
FormatConversionChar conv_ = FormatConversionCharInternal::kNone; FormatConversionChar conv_ = FormatConversionCharInternal::kNone;
Flags flags_; Flags flags_;
LengthMod length_mod_ = LengthMod::none;
int width_; int width_;
int precision_; int precision_;
}; };
...@@ -318,6 +321,9 @@ struct FormatConversionSpecImplFriend final { ...@@ -318,6 +321,9 @@ struct FormatConversionSpecImplFriend final {
static void SetFlags(Flags f, FormatConversionSpecImpl* conv) { static void SetFlags(Flags f, FormatConversionSpecImpl* conv) {
conv->flags_ = f; conv->flags_ = f;
} }
static void SetLengthMod(LengthMod l, FormatConversionSpecImpl* conv) {
conv->length_mod_ = l;
}
static void SetConversionChar(FormatConversionChar c, static void SetConversionChar(FormatConversionChar c,
FormatConversionSpecImpl* conv) { FormatConversionSpecImpl* conv) {
conv->conv_ = c; conv->conv_ = c;
......
...@@ -15,22 +15,23 @@ ...@@ -15,22 +15,23 @@
#ifndef ABSL_STRINGS_INTERNAL_STR_FORMAT_PARSER_H_ #ifndef ABSL_STRINGS_INTERNAL_STR_FORMAT_PARSER_H_
#define ABSL_STRINGS_INTERNAL_STR_FORMAT_PARSER_H_ #define ABSL_STRINGS_INTERNAL_STR_FORMAT_PARSER_H_
#include <limits.h>
#include <stddef.h> #include <stddef.h>
#include <stdlib.h> #include <stdlib.h>
#include <cassert> #include <cassert>
#include <cstdint> #include <cstring>
#include <initializer_list> #include <initializer_list>
#include <iosfwd>
#include <iterator>
#include <memory> #include <memory>
#include <string> #include <string>
#include <utility>
#include <vector> #include <vector>
#include "absl/base/config.h"
#include "absl/base/optimization.h"
#include "absl/strings/internal/str_format/checker.h" #include "absl/strings/internal/str_format/checker.h"
#include "absl/strings/internal/str_format/constexpr_parser.h" #include "absl/strings/internal/str_format/constexpr_parser.h"
#include "absl/strings/internal/str_format/extension.h" #include "absl/strings/internal/str_format/extension.h"
#include "absl/strings/string_view.h"
namespace absl { namespace absl {
ABSL_NAMESPACE_BEGIN ABSL_NAMESPACE_BEGIN
......
...@@ -15,10 +15,18 @@ ...@@ -15,10 +15,18 @@
#include "absl/strings/internal/str_format/parser.h" #include "absl/strings/internal/str_format/parser.h"
#include <string.h> #include <string.h>
#include <algorithm>
#include <initializer_list>
#include <string>
#include <utility>
#include "gmock/gmock.h" #include "gmock/gmock.h"
#include "gtest/gtest.h" #include "gtest/gtest.h"
#include "absl/base/config.h"
#include "absl/base/macros.h" #include "absl/base/macros.h"
#include "absl/strings/internal/str_format/constexpr_parser.h"
#include "absl/strings/internal/str_format/extension.h"
#include "absl/strings/string_view.h"
namespace absl { namespace absl {
ABSL_NAMESPACE_BEGIN ABSL_NAMESPACE_BEGIN
...@@ -303,7 +311,7 @@ TEST_F(ConsumeUnboundConversionTest, BasicFlag) { ...@@ -303,7 +311,7 @@ TEST_F(ConsumeUnboundConversionTest, BasicFlag) {
} }
// Flag is off // Flag is off
for (const char* fmt : {"3d", ".llx", "-G", "1$#X"}) { for (const char* fmt : {"3d", ".llx", "-G", "1$#X", "lc"}) {
SCOPED_TRACE(fmt); SCOPED_TRACE(fmt);
EXPECT_TRUE(Run(fmt)); EXPECT_TRUE(Run(fmt));
EXPECT_NE(o.flags, Flags::kBasic); EXPECT_NE(o.flags, Flags::kBasic);
......
...@@ -72,14 +72,20 @@ ...@@ -72,14 +72,20 @@
#ifndef ABSL_STRINGS_STR_FORMAT_H_ #ifndef ABSL_STRINGS_STR_FORMAT_H_
#define ABSL_STRINGS_STR_FORMAT_H_ #define ABSL_STRINGS_STR_FORMAT_H_
#include <cstdint>
#include <cstdio> #include <cstdio>
#include <string> #include <string>
#include <type_traits>
#include "absl/base/attributes.h"
#include "absl/base/config.h"
#include "absl/strings/internal/str_format/arg.h" // IWYU pragma: export #include "absl/strings/internal/str_format/arg.h" // IWYU pragma: export
#include "absl/strings/internal/str_format/bind.h" // IWYU pragma: export #include "absl/strings/internal/str_format/bind.h" // IWYU pragma: export
#include "absl/strings/internal/str_format/checker.h" // IWYU pragma: export #include "absl/strings/internal/str_format/checker.h" // IWYU pragma: export
#include "absl/strings/internal/str_format/extension.h" // IWYU pragma: export #include "absl/strings/internal/str_format/extension.h" // IWYU pragma: export
#include "absl/strings/internal/str_format/parser.h" // IWYU pragma: export #include "absl/strings/internal/str_format/parser.h" // IWYU pragma: export
#include "absl/strings/string_view.h"
#include "absl/types/span.h"
namespace absl { namespace absl {
ABSL_NAMESPACE_BEGIN ABSL_NAMESPACE_BEGIN
...@@ -256,7 +262,7 @@ class FormatCountCapture { ...@@ -256,7 +262,7 @@ class FormatCountCapture {
// //
// The `FormatSpec` intrinsically supports all of these fundamental C++ types: // The `FormatSpec` intrinsically supports all of these fundamental C++ types:
// //
// * Characters: `char`, `signed char`, `unsigned char` // * Characters: `char`, `signed char`, `unsigned char`, `wchar_t`
// * Integers: `int`, `short`, `unsigned short`, `unsigned`, `long`, // * Integers: `int`, `short`, `unsigned short`, `unsigned`, `long`,
// `unsigned long`, `long long`, `unsigned long long` // `unsigned long`, `long long`, `unsigned long long`
// * Enums: printed as their underlying integral value // * Enums: printed as their underlying integral value
...@@ -264,9 +270,9 @@ class FormatCountCapture { ...@@ -264,9 +270,9 @@ class FormatCountCapture {
// //
// However, in the `str_format` library, a format conversion specifies a broader // However, in the `str_format` library, a format conversion specifies a broader
// C++ conceptual category instead of an exact type. For example, `%s` binds to // C++ conceptual category instead of an exact type. For example, `%s` binds to
// any string-like argument, so `std::string`, `absl::string_view`, and // any string-like argument, so `std::string`, `std::wstring`,
// `const char*` are all accepted. Likewise, `%d` accepts any integer-like // `absl::string_view`, `const char*`, and `const wchar_t*` are all accepted.
// argument, etc. // Likewise, `%d` accepts any integer-like argument, etc.
template <typename... Args> template <typename... Args>
using FormatSpec = str_format_internal::FormatSpecTemplate< using FormatSpec = str_format_internal::FormatSpecTemplate<
......
...@@ -634,6 +634,10 @@ TEST(StrFormat, BehavesAsDocumented) { ...@@ -634,6 +634,10 @@ TEST(StrFormat, BehavesAsDocumented) {
const int& something = *reinterpret_cast<const int*>(ptr_value); const int& something = *reinterpret_cast<const int*>(ptr_value);
EXPECT_EQ(StrFormat("%p", &something), StrFormat("0x%x", ptr_value)); EXPECT_EQ(StrFormat("%p", &something), StrFormat("0x%x", ptr_value));
// The output of formatting a null pointer is not documented as being a
// specific thing, but the attempt should at least compile.
(void)StrFormat("%p", nullptr);
// Output widths are supported, with optional flags. // Output widths are supported, with optional flags.
EXPECT_EQ(StrFormat("%3d", 1), " 1"); EXPECT_EQ(StrFormat("%3d", 1), " 1");
EXPECT_EQ(StrFormat("%3d", 123456), "123456"); EXPECT_EQ(StrFormat("%3d", 123456), "123456");
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment