From 43e9b29e50b4904bf3210004e6d0e123ae70e13e Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Sun, 2 Feb 2020 08:29:33 -0800 Subject: [PATCH 01/45] Only use compiler features if available --- CMakeLists.txt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a645ef32..a62ee7e7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -69,7 +69,13 @@ set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} include(cxx14) include(CheckCXXCompilerFlag) -set(FMT_REQUIRED_FEATURES cxx_auto_type cxx_variadic_templates) +list(FIND CMAKE_CXX_COMPILE_FEATURES "cxx_variadic_templates" index) +if (${index} GREATER -1) + # Use cxx_variadic_templates instead of more appropriate cxx_std_11 for + # compatibility with older CMake versions. + set(FMT_REQUIRED_FEATURES cxx_variadic_templates) +endif () +message(STATUS "Required features: ${FMT_REQUIRED_FEATURES}") if (CMAKE_CXX_COMPILER_ID MATCHES "GNU") set(PEDANTIC_COMPILE_FLAGS -pedantic-errors -Wall -Wextra -pedantic From c54cd71800c85c313e5e868d3c73be8bc5c99a61 Mon Sep 17 00:00:00 2001 From: Jordan Williams <19399197+jwillikers@users.noreply.github.com> Date: Thu, 6 Feb 2020 19:39:23 -0600 Subject: [PATCH 02/45] only modify CMAKE_RUNTIME_OUTPUT_DIRECTORY if it is not already set --- CMakeLists.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a62ee7e7..6c2a5a84 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -61,7 +61,9 @@ message(STATUS "Version: ${FMT_VERSION}") message(STATUS "Build type: ${CMAKE_BUILD_TYPE}") -set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) +if (NOT CMAKE_RUNTIME_OUTPUT_DIRECTORY) + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) +endif () set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/support/cmake") From 24924128e32fba8011037e21454426458f99d13b Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Fri, 7 Feb 2020 18:34:05 -0800 Subject: [PATCH 03/45] Fix a link error in gcc8 (#1548) --- include/fmt/format.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/fmt/format.h b/include/fmt/format.h index 59dc223b..1f89b63e 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -312,7 +312,7 @@ template class is_output_iterator { using type = decltype(test(typename iterator_category::type{})); public: - static const bool value = !std::is_const>::value; + enum { value = !std::is_const>::value }; }; // A workaround for std::string not having mutable data() until C++17. From 1f1b50707c8f56095481e29594b0da24431f82d5 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Fri, 7 Feb 2020 18:47:48 -0800 Subject: [PATCH 04/45] Make formatter override implicit conversion to a C string --- include/fmt/core.h | 10 +++++----- test/core-test.cc | 15 +++++++++++++++ test/ostream-test.cc | 18 +++++------------- 3 files changed, 25 insertions(+), 18 deletions(-) diff --git a/include/fmt/core.h b/include/fmt/core.h index 6824c125..35c6414c 100644 --- a/include/fmt/core.h +++ b/include/fmt/core.h @@ -900,7 +900,8 @@ template struct arg_mapper { template , T>::value && - !is_string::value)> + !is_string::value && !has_formatter::value && + !has_fallback_formatter::value)> FMT_CONSTEXPR basic_string_view map(const T& val) { return basic_string_view(val); } @@ -909,7 +910,8 @@ template struct arg_mapper { FMT_ENABLE_IF( std::is_constructible, T>::value && !std::is_constructible, T>::value && - !is_string::value && !has_formatter::value)> + !is_string::value && !has_formatter::value && + !has_fallback_formatter::value)> FMT_CONSTEXPR basic_string_view map(const T& val) { return std_string_view(val); } @@ -946,10 +948,8 @@ template struct arg_mapper { typename T, FMT_ENABLE_IF( !is_string::value && !is_char::value && - !std::is_constructible, T>::value && (has_formatter::value || - (has_fallback_formatter::value && - !std::is_constructible, T>::value)))> + has_fallback_formatter::value))> FMT_CONSTEXPR const T& map(const T& val) { return val; } diff --git a/test/core-test.cc b/test/core-test.cc index 8f233ece..6a7c0d3b 100644 --- a/test/core-test.cc +++ b/test/core-test.cc @@ -469,6 +469,10 @@ struct convertible_to_int { operator int() const { return 42; } }; +struct convertible_to_c_string { + operator const char*() const { return "foo"; } +}; + FMT_BEGIN_NAMESPACE template <> struct formatter { auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) { @@ -478,10 +482,21 @@ template <> struct formatter { return std::copy_n("foo", 3, ctx.out()); } }; + +template <> struct formatter { + auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) { + return ctx.begin(); + } + auto format(convertible_to_c_string, format_context& ctx) + -> decltype(ctx.out()) { + return std::copy_n("bar", 3, ctx.out()); + } +}; FMT_END_NAMESPACE TEST(CoreTest, FormatterOverridesImplicitConversion) { EXPECT_EQ(fmt::format("{}", convertible_to_int()), "foo"); + EXPECT_EQ(fmt::format("{}", convertible_to_c_string()), "bar"); } namespace my_ns { diff --git a/test/ostream-test.cc b/test/ostream-test.cc index 9944618e..d9329345 100644 --- a/test/ostream-test.cc +++ b/test/ostream-test.cc @@ -264,17 +264,13 @@ struct explicitly_convertible_to_string_like { } }; -TEST(FormatterTest, FormatExplicitlyConvertibleToStringLike) { - EXPECT_EQ("foo", fmt::format("{}", explicitly_convertible_to_string_like())); -} - std::ostream& operator<<(std::ostream& os, explicitly_convertible_to_string_like) { return os << "bar"; } -TEST(FormatterTest, FormatExplicitlyConvertibleToStringLikeIgnoreInserter) { - EXPECT_EQ("foo", fmt::format("{}", explicitly_convertible_to_string_like())); +TEST(FormatterTest, FormatExplicitlyConvertibleToStringLike) { + EXPECT_EQ("bar", fmt::format("{}", explicitly_convertible_to_string_like())); } #ifdef FMT_USE_STRING_VIEW @@ -284,17 +280,13 @@ struct explicitly_convertible_to_std_string_view { } }; -TEST(FormatterTest, FormatExplicitlyConvertibleToStdStringView) { - EXPECT_EQ("foo", fmt::format("{}", explicitly_convertible_to_string_like())); -} - std::ostream& operator<<(std::ostream& os, explicitly_convertible_to_std_string_view) { return os << "bar"; } -TEST(FormatterTest, FormatExplicitlyConvertibleToStdStringViewIgnoreInserter) { - EXPECT_EQ("foo", - fmt::format("{}", explicitly_convertible_to_std_string_view())); +TEST(FormatterTest, FormatExplicitlyConvertibleToStdStringView) { + EXPECT_EQ("bar", fmt::format("{}", explicitly_convertible_to_string_like())); } + #endif // FMT_USE_STRING_VIEW From dc22360c34884a2ea52e1ed27888ddc9fb13c307 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Mon, 10 Feb 2020 17:08:14 +0100 Subject: [PATCH 05/45] Workaround broken UDL templates in GCC < 6.4 --- include/fmt/format.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/fmt/format.h b/include/fmt/format.h index 1f89b63e..6145e4e4 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -131,11 +131,11 @@ FMT_END_NAMESPACE #endif #ifndef FMT_USE_UDL_TEMPLATE -// EDG front end based compilers (icc, nvcc) do not support UDL templates yet -// and GCC 9 warns about them. +// EDG front end based compilers (icc, nvcc) and GCC < 6.4 do not propertly +// support UDL templates and GCC >= 9 warns about them. # if FMT_USE_USER_DEFINED_LITERALS && FMT_ICC_VERSION == 0 && \ FMT_CUDA_VERSION == 0 && \ - ((FMT_GCC_VERSION >= 600 && FMT_GCC_VERSION <= 900 && \ + ((FMT_GCC_VERSION >= 604 && FMT_GCC_VERSION <= 900 && \ __cplusplus >= 201402L) || \ FMT_CLANG_VERSION >= 304) # define FMT_USE_UDL_TEMPLATE 1 From f733882b55779c68d2b1fbbe978f10c11113b6cd Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Fri, 14 Feb 2020 13:54:15 +0100 Subject: [PATCH 06/45] Remove misleading FMT_USE_WINDOWS_H --- include/fmt/os.h | 11 ++--------- src/os.cc | 6 +++--- test/CMakeLists.txt | 12 ------------ 3 files changed, 5 insertions(+), 24 deletions(-) diff --git a/include/fmt/os.h b/include/fmt/os.h index b16441cb..95fafcf1 100644 --- a/include/fmt/os.h +++ b/include/fmt/os.h @@ -132,15 +132,8 @@ class error_code { int get() const FMT_NOEXCEPT { return value_; } }; -// Define FMT_USE_WINDOWS_H to 0 to disable use of windows.h. -// All the functionality that relies on it will be disabled too. -#ifndef _WIN32 -# define FMT_USE_WINDOWS_H 0 -#elif !defined(FMT_USE_WINDOWS_H) -# define FMT_USE_WINDOWS_H 1 -#endif -#if FMT_USE_WINDOWS_H +#ifdef _WIN32 namespace internal { // A converter from UTF-16 to UTF-8. // It is only provided for Windows since other systems support UTF-8 natively. @@ -210,7 +203,7 @@ class windows_error : public system_error { // Can be used to report errors from destructors. FMT_API void report_windows_error(int error_code, string_view message) FMT_NOEXCEPT; -#endif +#endif // _WIN32 // A buffered file. class buffered_file { diff --git a/src/os.cc b/src/os.cc index acec6cfc..10a372a4 100644 --- a/src/os.cc +++ b/src/os.cc @@ -44,7 +44,7 @@ # endif // _WIN32 #endif // FMT_USE_FCNTL -#if FMT_USE_WINDOWS_H +#ifdef _WIN32 # include #endif @@ -72,7 +72,7 @@ inline std::size_t convert_rwcount(std::size_t count) { return count; } FMT_BEGIN_NAMESPACE -#if FMT_USE_WINDOWS_H +#ifdef _WIN32 internal::utf16_to_utf8::utf16_to_utf8(wstring_view s) { if (int error_code = convert(s)) { FMT_THROW(windows_error(error_code, @@ -145,7 +145,7 @@ void report_windows_error(int error_code, fmt::string_view message) FMT_NOEXCEPT { report_error(internal::format_windows_error, error_code, message); } -#endif // FMT_USE_WINDOWS_H +#endif // _WIN32 buffered_file::~buffered_file() FMT_NOEXCEPT { if (file_ && FMT_SYSTEM(fclose(file_)) != 0) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 73919da0..ad391c22 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -171,18 +171,6 @@ if (FMT_PEDANTIC) target_compile_definitions( nolocale-test PRIVATE FMT_STATIC_THOUSANDS_SEPARATOR=1) - # Test that the library compiles without windows.h. - if (CMAKE_SYSTEM_NAME STREQUAL "Windows") - add_library(no-windows-h-test ../src/format.cc) - target_include_directories( - no-windows-h-test PRIVATE ${PROJECT_SOURCE_DIR}/include) - target_compile_definitions(no-windows-h-test PRIVATE FMT_USE_WINDOWS_H=0) - if (FMT_PEDANTIC) - target_compile_options(no-windows-h-test PRIVATE ${PEDANTIC_COMPILE_FLAGS}) - endif () - target_include_directories(no-windows-h-test SYSTEM PUBLIC gtest gmock) - endif () - add_test(compile-error-test ${CMAKE_CTEST_COMMAND} --build-and-test "${CMAKE_CURRENT_SOURCE_DIR}/compile-error-test" From 3bafd0749b8d1493bc728bd823b8a68297fd6fa4 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Sat, 15 Feb 2020 09:51:35 +0100 Subject: [PATCH 07/45] Fix to_string docs --- include/fmt/format.h | 1 - 1 file changed, 1 deletion(-) diff --git a/include/fmt/format.h b/include/fmt/format.h index 6145e4e4..c9abfe48 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -3238,7 +3238,6 @@ arg_join, wchar_t> join(const Range& range, /** \rst Converts *value* to ``std::string`` using the default format for type *T*. - It doesn't support user-defined types with custom formatters. **Example**:: From 0415cf235000c449ea986a2c46641f6787c8da90 Mon Sep 17 00:00:00 2001 From: dominicpoeschko <45942148+dominicpoeschko@users.noreply.github.com> Date: Wed, 19 Feb 2020 14:59:50 +0100 Subject: [PATCH 08/45] add const begin and end overload to buffer (#1553) * add const begin and end overload to buffer since there is a const overload for data I think there should also be one for begin and end --- include/fmt/core.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/include/fmt/core.h b/include/fmt/core.h index 35c6414c..1bb58793 100644 --- a/include/fmt/core.h +++ b/include/fmt/core.h @@ -633,6 +633,9 @@ template class buffer { T* begin() FMT_NOEXCEPT { return ptr_; } T* end() FMT_NOEXCEPT { return ptr_ + size_; } + const T* begin() const FMT_NOEXCEPT { return ptr_; } + const T* end() const FMT_NOEXCEPT { return ptr_ + size_; } + /** Returns the size of this buffer. */ std::size_t size() const FMT_NOEXCEPT { return size_; } From e00997b004713a8de4ea480d7dfb3abb11d07852 Mon Sep 17 00:00:00 2001 From: IkarusDeveloper <52773127+IkarusDeveloper@users.noreply.github.com> Date: Fri, 21 Feb 2020 23:43:07 +0100 Subject: [PATCH 09/45] improved use of find (#1560) * improved use of find *begin is supposed to be different from '{' when this find is used, so we can avoid checking it. --- include/fmt/format.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/fmt/format.h b/include/fmt/format.h index c9abfe48..f74f53e9 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -2543,7 +2543,7 @@ FMT_CONSTEXPR void parse_format_string(basic_string_view format_str, // Doing two passes with memchr (one for '{' and another for '}') is up to // 2.5x faster than the naive one-pass implementation on big format strings. const Char* p = begin; - if (*begin != '{' && !find(begin, end, '{', p)) + if (*begin != '{' && !find(begin + 1, end, '{', p)) return write(begin, end); write(begin, p); ++p; From 2161a73f2bbd723700ca6cebe1ae49d41201a10e Mon Sep 17 00:00:00 2001 From: fghzxm Date: Sun, 23 Feb 2020 23:27:22 +0800 Subject: [PATCH 10/45] Fix FMT_FORMAT_AS const specifier position (#1554) The current `FMT_FORMAT_AS` macro will make `formatter::format` have the first argument type `const Char *&` which is incorrect an should be `Char *const &`. This pull request fixes that by changing the first argument type in the macro definition body from `const Type &` to `Type const &`. --- include/fmt/format.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/fmt/format.h b/include/fmt/format.h index f74f53e9..ed87ea3d 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -2953,7 +2953,7 @@ struct formatter \ struct formatter : formatter { \ template \ - auto format(const Type& val, FormatContext& ctx) -> decltype(ctx.out()) { \ + auto format(Type const& val, FormatContext& ctx) -> decltype(ctx.out()) { \ return formatter::format(val, ctx); \ } \ } From 13d82e32bd8516c95861dc52521625e5d2a73071 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Mon, 24 Feb 2020 11:32:34 -0800 Subject: [PATCH 11/45] Don't use internal GTest API --- test/assert-test.cc | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/test/assert-test.cc b/test/assert-test.cc index 26a87a7b..bc728b53 100644 --- a/test/assert-test.cc +++ b/test/assert-test.cc @@ -8,17 +8,12 @@ #include "fmt/core.h" #include "gtest.h" -#if GTEST_HAS_DEATH_TEST -# define EXPECT_DEBUG_DEATH_IF_SUPPORTED(statement, regex) \ - EXPECT_DEBUG_DEATH(statement, regex) -#else -# define EXPECT_DEBUG_DEATH_IF_SUPPORTED(statement, regex) \ - GTEST_UNSUPPORTED_DEATH_TEST_(statement, regex, ) -#endif - TEST(AssertTest, Fail) { - EXPECT_DEBUG_DEATH_IF_SUPPORTED(FMT_ASSERT(false, "don't panic!"), - "don't panic!"); +#if GTEST_HAS_DEATH_TEST + EXPECT_DEBUG_DEATH(FMT_ASSERT(false, "don't panic!"), "don't panic!"); +#else + fmt::print("warning: death tests are not supported\n"); +#endif } bool test_condition = false; From b2d3a86ec0865c2771191d674e85d31d22d5b1da Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Wed, 26 Feb 2020 06:26:46 -0800 Subject: [PATCH 12/45] Make FMT_ASSERT work in constexpr on clang 4.0.1 --- include/fmt/core.h | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/include/fmt/core.h b/include/fmt/core.h index 1bb58793..76fedf05 100644 --- a/include/fmt/core.h +++ b/include/fmt/core.h @@ -259,9 +259,9 @@ FMT_API void assert_fail(const char* file, int line, const char* message); # ifdef NDEBUG # define FMT_ASSERT(condition, message) # else -# define FMT_ASSERT(condition, message) \ - ((condition) \ - ? void() \ +# define FMT_ASSERT(condition, message) \ + ((condition) /* void() fails with -Winvalid-constexpr on clang 4.0.1 */ \ + ? (void)0 \ : ::fmt::internal::assert_fail(__FILE__, __LINE__, (message))) # endif #endif @@ -947,12 +947,10 @@ template struct arg_mapper { map(static_cast::type>(val))) { return map(static_cast::type>(val)); } - template < - typename T, - FMT_ENABLE_IF( - !is_string::value && !is_char::value && - (has_formatter::value || - has_fallback_formatter::value))> + template ::value && !is_char::value && + (has_formatter::value || + has_fallback_formatter::value))> FMT_CONSTEXPR const T& map(const T& val) { return val; } From bed134a4aad3de93b2ba0e1a59a51b662243b7e8 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Thu, 27 Feb 2020 15:29:46 -0800 Subject: [PATCH 13/45] Tentative fix for default template param in friend error --- include/fmt/format-inl.h | 41 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/include/fmt/format-inl.h b/include/fmt/format-inl.h index 55bf1c44..1ac206a8 100644 --- a/include/fmt/format-inl.h +++ b/include/fmt/format-inl.h @@ -336,6 +336,10 @@ class fp { private: using significand_type = uint64_t; + public: + significand_type f; + int e; + // All sizes are in bits. // Subtract 1 to account for an implicit most significant bit in the // normalized form. @@ -343,11 +347,6 @@ class fp { std::numeric_limits::digits - 1; static FMT_CONSTEXPR_DECL const uint64_t implicit_bit = 1ULL << double_significand_size; - - public: - significand_type f; - int e; - static FMT_CONSTEXPR_DECL const int significand_size = bits::value; @@ -358,22 +357,6 @@ class fp { // errors on platforms where double is not IEEE754. template explicit fp(Double d) { assign(d); } - // Normalizes the value converted from double and multiplied by (1 << SHIFT). - template friend fp normalize(fp value) { - // Handle subnormals. - const auto shifted_implicit_bit = fp::implicit_bit << SHIFT; - while ((value.f & shifted_implicit_bit) == 0) { - value.f <<= 1; - --value.e; - } - // Subtract 1 to account for hidden bit. - const auto offset = - fp::significand_size - fp::double_significand_size - SHIFT - 1; - value.f <<= offset; - value.e -= offset; - return value; - } - // Assigns d to this and return true iff predecessor is closer than successor. template bool assign(Double d) { @@ -434,6 +417,22 @@ class fp { } }; +// Normalizes the value converted from double and multiplied by (1 << SHIFT). +template fp normalize(fp value) { + // Handle subnormals. + const auto shifted_implicit_bit = fp::implicit_bit << SHIFT; + while ((value.f & shifted_implicit_bit) == 0) { + value.f <<= 1; + --value.e; + } + // Subtract 1 to account for hidden bit. + const auto offset = + fp::significand_size - fp::double_significand_size - SHIFT - 1; + value.f <<= offset; + value.e -= offset; + return value; +} + inline bool operator==(fp x, fp y) { return x.f == y.f && x.e == y.e; } // Computes lhs * rhs / pow(2, 64) rounded to nearest with half-up tie breaking. From 6ccb2e241b57a3a993f794f19e4b7ceb14b56d1f Mon Sep 17 00:00:00 2001 From: refnum <68672+refnum@users.noreply.github.com> Date: Sun, 1 Mar 2020 01:17:55 +0000 Subject: [PATCH 14/45] Add FMT_NORETURN to assert_fail prototype. (#1569) When building with -Werror,-Wmissing-noreturn clang identifies that assert_fail could be declared with the 'noreturn' attribute. --- include/fmt/core.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/fmt/core.h b/include/fmt/core.h index 76fedf05..a107db3c 100644 --- a/include/fmt/core.h +++ b/include/fmt/core.h @@ -253,7 +253,7 @@ namespace internal { // A workaround for gcc 4.8 to make void_t work in a SFINAE context. template struct void_t_impl { using type = void; }; -FMT_API void assert_fail(const char* file, int line, const char* message); +FMT_NORETURN FMT_API void assert_fail(const char* file, int line, const char* message); #ifndef FMT_ASSERT # ifdef NDEBUG From 75a4525e5f1d63d082c21ebce51ca0d474a682e4 Mon Sep 17 00:00:00 2001 From: refnum <68672+refnum@users.noreply.github.com> Date: Sun, 1 Mar 2020 01:19:34 +0000 Subject: [PATCH 15/45] Move FMT_CLANG_VERSION definition to core.h (#1568) Previously format.h defined FMT_CLANG_VERSION after including core.h, however core.h tests FMT_CLANG_VERSION when it defines FMT_API. --- include/fmt/core.h | 6 ++++++ include/fmt/format.h | 6 ------ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/include/fmt/core.h b/include/fmt/core.h index a107db3c..f5eb7e3a 100644 --- a/include/fmt/core.h +++ b/include/fmt/core.h @@ -36,6 +36,12 @@ # define FMT_HAS_CPP_ATTRIBUTE(x) 0 #endif +#ifdef __clang__ +# define FMT_CLANG_VERSION (__clang_major__ * 100 + __clang_minor__) +#else +# define FMT_CLANG_VERSION 0 +#endif + #if defined(__GNUC__) && !defined(__clang__) # define FMT_GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__) #else diff --git a/include/fmt/format.h b/include/fmt/format.h index ed87ea3d..acd7d9dc 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -43,12 +43,6 @@ #include "core.h" -#ifdef __clang__ -# define FMT_CLANG_VERSION (__clang_major__ * 100 + __clang_minor__) -#else -# define FMT_CLANG_VERSION 0 -#endif - #ifdef __INTEL_COMPILER # define FMT_ICC_VERSION __INTEL_COMPILER #elif defined(__ICL) From 58e6c84f5a3258a9cf9cd465fd9b1a9423d62f8e Mon Sep 17 00:00:00 2001 From: refnum <68672+refnum@users.noreply.github.com> Date: Sun, 1 Mar 2020 15:22:15 +0000 Subject: [PATCH 16/45] Fix simple -Wsign-conversion cases. (#1571) * Fix -Wsign-conversion in bigint::subtract_aligned. n is assigned a size_t, and only used for comparisons with j. j is assigned 0, compared to n (size_t), and passed to basic_memory_buffer::operator[] (size_t). * Fix -Wsign-conversion in bigint::assign. num_bigits is initialised to 0, is only ever incremented, and is passed to basic_memory_buffer::operator[] (size_t) and basic_memory_buffer::resize (size_t). --- include/fmt/format-inl.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/fmt/format-inl.h b/include/fmt/format-inl.h index 1ac206a8..4ad4df4c 100644 --- a/include/fmt/format-inl.h +++ b/include/fmt/format-inl.h @@ -526,7 +526,7 @@ class bigint { FMT_ASSERT(compare(*this, other) >= 0, ""); bigit borrow = 0; int i = other.exp_ - exp_; - for (int j = 0, n = static_cast(other.bigits_.size()); j != n; + for (size_t j = 0, n = other.bigits_.size(); j != n; ++i, ++j) { subtract_bigits(i, other.bigits_[j], borrow); } @@ -578,7 +578,7 @@ class bigint { } void assign(uint64_t n) { - int num_bigits = 0; + size_t num_bigits = 0; do { bigits_[num_bigits++] = n & ~bigit(0); n >>= bigit_bits; From 1e8493196efa4e05baa22461ba61b636ab2e662b Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Sun, 1 Mar 2020 07:57:34 -0800 Subject: [PATCH 17/45] Make compile-time checks in format_to handle references --- include/fmt/core.h | 2 +- include/fmt/format.h | 7 +++---- test/format-test.cc | 2 ++ 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/include/fmt/core.h b/include/fmt/core.h index f5eb7e3a..0e6422d9 100644 --- a/include/fmt/core.h +++ b/include/fmt/core.h @@ -1460,7 +1460,7 @@ make_args_checked(const S& format_str, all_true<(!std::is_base_of>::value || !std::is_reference::value)...>::value, "passing views as lvalues is disallowed"); - check_format_string>...>(format_str); + check_format_string(format_str); return {args...}; } diff --git a/include/fmt/format.h b/include/fmt/format.h index acd7d9dc..5d36a682 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -2641,10 +2641,9 @@ FMT_CONSTEXPR bool do_check_format_string(basic_string_view s, template ::value), int>> void check_format_string(S format_str) { - FMT_CONSTEXPR_DECL bool invalid_format = - internal::do_check_format_string( - to_string_view(format_str)); + FMT_CONSTEXPR_DECL bool invalid_format = internal::do_check_format_string< + typename S::char_type, internal::error_handler, + remove_const_t>...>(to_string_view(format_str)); (void)invalid_format; } diff --git a/test/format-test.cc b/test/format-test.cc index 8bce69be..b1730630 100644 --- a/test/format-test.cc +++ b/test/format-test.cc @@ -1859,6 +1859,8 @@ TEST(FormatTest, CustomFormatCompileTimeString) { EXPECT_EQ("42", fmt::format(FMT_STRING("{}"), Answer())); Answer answer; EXPECT_EQ("42", fmt::format(FMT_STRING("{}"), answer)); + char buf[10] = {}; + fmt::format_to(buf, FMT_STRING("{}"), answer); const Answer const_answer = Answer(); EXPECT_EQ("42", fmt::format(FMT_STRING("{}"), const_answer)); } From 68742e1d8756dd0a51585cff0d7da2034e05a4ca Mon Sep 17 00:00:00 2001 From: refnum <68672+refnum@users.noreply.github.com> Date: Wed, 4 Mar 2020 14:48:41 +0000 Subject: [PATCH 18/45] Fix clang -Wsign-conversion warning in grisu_count_digits. (#1573) grisu_count_digits is only used by grisu_gen_digits, which assigns the unsigned result to a (signed) int. Although grisu_count_digits always returns a positive integer this keeps its return type in sync with the type its result is assigned to. --- include/fmt/format-inl.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/fmt/format-inl.h b/include/fmt/format-inl.h index 4ad4df4c..0872fcbb 100644 --- a/include/fmt/format-inl.h +++ b/include/fmt/format-inl.h @@ -756,7 +756,7 @@ enum result { } // A version of count_digits optimized for grisu_gen_digits. -inline unsigned grisu_count_digits(uint32_t n) { +inline int grisu_count_digits(uint32_t n) { if (n < 10) return 1; if (n < 100) return 2; if (n < 1000) return 3; From 197a5c372101ce5e8774aec34ddd99a8c0949ebe Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Wed, 4 Mar 2020 07:44:02 -0800 Subject: [PATCH 19/45] Apply clang-format --- include/fmt/format-inl.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/include/fmt/format-inl.h b/include/fmt/format-inl.h index 0872fcbb..c42f7280 100644 --- a/include/fmt/format-inl.h +++ b/include/fmt/format-inl.h @@ -526,8 +526,7 @@ class bigint { FMT_ASSERT(compare(*this, other) >= 0, ""); bigit borrow = 0; int i = other.exp_ - exp_; - for (size_t j = 0, n = other.bigits_.size(); j != n; - ++i, ++j) { + for (size_t j = 0, n = other.bigits_.size(); j != n; ++i, ++j) { subtract_bigits(i, other.bigits_[j], borrow); } while (borrow > 0) subtract_bigits(i, 0, borrow); From 0c6919ec7202bdfba6cb7911d7ad6b69d91dcd09 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Wed, 4 Mar 2020 07:44:23 -0800 Subject: [PATCH 20/45] Make FMT_DEBUG_POSTFIX a cache variable (#1566) --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6c2a5a84..3d668fcb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -182,7 +182,7 @@ target_include_directories(fmt PUBLIC $ $) -set(FMT_DEBUG_POSTFIX d) +set(FMT_DEBUG_POSTFIX d CACHE STRING "Debug library postfix.") set_target_properties(fmt PROPERTIES VERSION ${FMT_VERSION} SOVERSION ${CPACK_PACKAGE_VERSION_MAJOR} From eafd079868d5f253e07103051a0ce2a06c20661a Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Wed, 4 Mar 2020 16:43:49 -0800 Subject: [PATCH 21/45] Improve width computation --- include/fmt/format.h | 50 ++++++++++++++++++++++++++++++++++++++++---- test/format-test.cc | 3 +++ 2 files changed, 49 insertions(+), 4 deletions(-) diff --git a/include/fmt/format.h b/include/fmt/format.h index 5d36a682..a5199b67 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -473,8 +473,8 @@ inline size_t count_code_points(basic_string_view s) { } // Counts the number of code points in a UTF-8 string. -inline size_t count_code_points(basic_string_view s) { - const char8_t* data = s.data(); +inline size_t count_code_points(basic_string_view s) { + const char* data = s.data(); size_t num_code_points = 0; for (size_t i = 0, size = s.size(); i != size; ++i) { if ((data[i] & 0xc0) != 0x80) ++num_code_points; @@ -482,6 +482,11 @@ inline size_t count_code_points(basic_string_view s) { return num_code_points; } +inline size_t count_code_points(basic_string_view s) { + return count_code_points(basic_string_view( + reinterpret_cast(s.data()), s.size())); +} + template inline size_t code_point_index(basic_string_view s, size_t n) { size_t size = s.size(); @@ -1603,6 +1608,18 @@ template class basic_writer { } }; + struct bytes_writer { + string_view bytes; + + size_t size() const { return bytes.size(); } + size_t width() const { return bytes.size(); } + + template void operator()(It&& it) const { + const char* data = bytes.data(); + it = copy_str(data, data + size(), it); + } + }; + template struct pointer_writer { UIntPtr value; int num_digits; @@ -1761,6 +1778,10 @@ template class basic_writer { write(data, size, specs); } + void write_bytes(string_view bytes, const format_specs& specs) { + write_padded(specs, bytes_writer{bytes}); + } + template void write_pointer(UIntPtr value, const format_specs* specs) { int num_digits = count_digits<4>(value); @@ -3150,11 +3171,32 @@ class bytes { explicit bytes(string_view data) : data_(data) {} }; -template <> struct formatter : formatter { +template <> struct formatter { + template + FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { + using handler_type = internal::dynamic_specs_handler; + internal::specs_checker handler(handler_type(specs_, ctx), + internal::type::string_type); + auto it = parse_format_specs(ctx.begin(), ctx.end(), handler); + internal::check_string_type_spec(specs_.type, ctx.error_handler()); + return it; + } + template auto format(bytes b, FormatContext& ctx) -> decltype(ctx.out()) { - return formatter::format(b.data_, ctx); + internal::handle_dynamic_spec( + specs_.width, specs_.width_ref, ctx); + internal::handle_dynamic_spec( + specs_.precision, specs_.precision_ref, ctx); + using range_type = + internal::output_range; + internal::basic_writer writer(range_type(ctx.out())); + writer.write_bytes(b.data_, specs_); + return writer.out(); } + + private: + internal::dynamic_format_specs specs_; }; template struct arg_join : internal::view { diff --git a/test/format-test.cc b/test/format-test.cc index b1730630..070c5033 100644 --- a/test/format-test.cc +++ b/test/format-test.cc @@ -6,6 +6,7 @@ // For the license information refer to format.h. #include + #include #include #include @@ -987,7 +988,9 @@ TEST(FormatterTest, Width) { EXPECT_EQ(" 0xcafe", format("{0:10}", reinterpret_cast(0xcafe))); EXPECT_EQ("x ", format("{0:11}", 'x')); EXPECT_EQ("str ", format("{0:12}", "str")); + EXPECT_EQ(fmt::format("{:*^5}", "🤡"), "**🤡**"); } + template inline T const_check(T value) { return value; } TEST(FormatterTest, RuntimeWidth) { From 153f753bde62027bd09f42284e58ea59f8170b49 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Wed, 4 Mar 2020 19:20:19 -0800 Subject: [PATCH 22/45] Deprecate undocumented _u suffix --- include/fmt/format.h | 3 ++- test/format-test.cc | 12 ------------ 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/include/fmt/format.h b/include/fmt/format.h index a5199b67..7af10895 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -566,7 +566,8 @@ class FMT_DEPRECATED u8string_view : public basic_string_view { #if FMT_USE_USER_DEFINED_LITERALS inline namespace literals { -inline basic_string_view operator"" _u(const char* s, std::size_t n) { +FMT_DEPRECATED inline basic_string_view operator"" _u(const char* s, + std::size_t n) { return {reinterpret_cast(s), n}; } } // namespace literals diff --git a/test/format-test.cc b/test/format-test.cc index 070c5033..92ad5369 100644 --- a/test/format-test.cc +++ b/test/format-test.cc @@ -2523,18 +2523,6 @@ std::string from_u8str(const fmt::basic_string_view& str) { return std::string(str.begin(), str.end()); } -#if FMT_USE_USER_DEFINED_LITERALS -TEST(FormatTest, U8StringViewLiteral) { - using namespace fmt::literals; - fmt::basic_string_view s = "ab"_u; - EXPECT_EQ(s.size(), 2u); - const char8_t* data = s.data(); - EXPECT_EQ(char(data[0]), 'a'); - EXPECT_EQ(char(data[1]), 'b'); - EXPECT_EQ(from_u8str(format("{:*^5}"_u, "🤡"_u)), from_u8str("**🤡**"_u)); -} -#endif - TEST(FormatTest, EmphasisNonHeaderOnly) { // Ensure this compiles even if FMT_HEADER_ONLY is not defined. EXPECT_EQ(fmt::format(fmt::emphasis::bold, "bold error"), From 8a06ca84c7995a1b6966845ecb4d376e6fde4ea0 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Thu, 5 Mar 2020 11:43:33 -0800 Subject: [PATCH 23/45] Fix ambiguous overloads of format & format_to --- include/fmt/compile.h | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/include/fmt/compile.h b/include/fmt/compile.h index 2e166b5c..ca8b4285 100644 --- a/include/fmt/compile.h +++ b/include/fmt/compile.h @@ -351,6 +351,8 @@ template struct get_type_impl> { template using get_type = typename get_type_impl::type; +template struct is_static_compiled_format : std::false_type {}; + template struct text { basic_string_view data; using char_type = Char; @@ -362,6 +364,9 @@ template struct text { } }; +template +struct is_static_compiled_format> : std::true_type {}; + template constexpr text make_text(basic_string_view s, size_t pos, size_t size) { @@ -407,6 +412,9 @@ template struct field { } }; +template +struct is_static_compiled_format> : std::true_type {}; + template struct concat { L lhs; R rhs; @@ -419,6 +427,9 @@ template struct concat { } }; +template +struct is_static_compiled_format> : std::true_type {}; + template constexpr concat make_concat(L lhs, R rhs) { return {lhs, rhs}; @@ -507,19 +518,19 @@ constexpr auto compile(S format_str) { } } -template ::value)> +template < + typename CompiledFormat, typename... Args, + typename Char = typename CompiledFormat::char_type, + FMT_ENABLE_IF(internal::is_static_compiled_format::value)> std::basic_string format(const CompiledFormat& cf, const Args&... args) { basic_memory_buffer buffer; cf.format(std::back_inserter(buffer), args...); return to_string(buffer); } -template ::value)> +template < + typename OutputIt, typename CompiledFormat, typename... Args, + FMT_ENABLE_IF(internal::is_static_compiled_format::value)> OutputIt format_to(OutputIt out, const CompiledFormat& cf, const Args&... args) { return cf.format(out, args...); From 29a1ea795accb1ef12d4648cc15071bad8fd8b2b Mon Sep 17 00:00:00 2001 From: Dair Grant Date: Mon, 2 Mar 2020 15:08:56 +0000 Subject: [PATCH 24/45] Fix clang -Wdisabled-macro-expansion warning from FMT_STRING_IMPL. FMT_STRING_IMPL has an internal helper named FMT_STRING, however FMT_STRING is also the name of the macro that invokes FMT_STRING_IMPL. Renaming this helper avoids the appearance of a recursive macro. --- include/fmt/format.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/fmt/format.h b/include/fmt/format.h index 7af10895..16b77fa5 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -3547,7 +3547,7 @@ FMT_END_NAMESPACE #define FMT_STRING_IMPL(s, ...) \ [] { \ /* Use a macro-like name to avoid shadowing warnings. */ \ - struct FMT_STRING : fmt::compile_string { \ + struct FMT_COMPILE_STRING : fmt::compile_string { \ using char_type = fmt::remove_cvref_t; \ __VA_ARGS__ FMT_CONSTEXPR \ operator fmt::basic_string_view() const { \ @@ -3555,7 +3555,7 @@ FMT_END_NAMESPACE return fmt::internal::literal_to_view(s); \ } \ }; \ - return FMT_STRING(); \ + return FMT_COMPILE_STRING(); \ }() /** From db4a6cfbf946cba3ce3d0471a45f21e55916f6ca Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Fri, 6 Mar 2020 07:25:34 -0800 Subject: [PATCH 25/45] is_static_compiled_format -> is_compiled_format --- include/fmt/compile.h | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/include/fmt/compile.h b/include/fmt/compile.h index ca8b4285..e4b12f34 100644 --- a/include/fmt/compile.h +++ b/include/fmt/compile.h @@ -351,7 +351,7 @@ template struct get_type_impl> { template using get_type = typename get_type_impl::type; -template struct is_static_compiled_format : std::false_type {}; +template struct is_compiled_format : std::false_type {}; template struct text { basic_string_view data; @@ -365,7 +365,7 @@ template struct text { }; template -struct is_static_compiled_format> : std::true_type {}; +struct is_compiled_format> : std::true_type {}; template constexpr text make_text(basic_string_view s, size_t pos, @@ -413,7 +413,7 @@ template struct field { }; template -struct is_static_compiled_format> : std::true_type {}; +struct is_compiled_format> : std::true_type {}; template struct concat { L lhs; @@ -428,7 +428,7 @@ template struct concat { }; template -struct is_static_compiled_format> : std::true_type {}; +struct is_compiled_format> : std::true_type {}; template constexpr concat make_concat(L lhs, R rhs) { @@ -518,19 +518,17 @@ constexpr auto compile(S format_str) { } } -template < - typename CompiledFormat, typename... Args, - typename Char = typename CompiledFormat::char_type, - FMT_ENABLE_IF(internal::is_static_compiled_format::value)> +template ::value)> std::basic_string format(const CompiledFormat& cf, const Args&... args) { basic_memory_buffer buffer; cf.format(std::back_inserter(buffer), args...); return to_string(buffer); } -template < - typename OutputIt, typename CompiledFormat, typename... Args, - FMT_ENABLE_IF(internal::is_static_compiled_format::value)> +template ::value)> OutputIt format_to(OutputIt out, const CompiledFormat& cf, const Args&... args) { return cf.format(out, args...); From ce00979152322352461b4f102e5ea4c3e556b327 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Sat, 7 Mar 2020 08:18:01 -0800 Subject: [PATCH 26/45] Cleanup CMake config --- CMakeLists.txt | 44 ++++++++++++++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 14 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3d668fcb..11169ce7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,13 +24,24 @@ function(join result_var) set(${result_var} "${result}" PARENT_SCOPE) endfunction() +# Sets a cache variable with a docstring joined from multiple arguments: +# set( ... CACHE ...) +# This allows splitting a long docstring for readability. +function(set_doc) + cmake_parse_arguments(SET_DOC "" "" "CACHE" ${ARGN}) + list(GET SET_DOC_CACHE 0 type) + list(REMOVE_AT SET_DOC_CACHE 0) + join(doc ${SET_DOC_CACHE}) + set(${SET_DOC_UNPARSED_ARGUMENTS} CACHE ${type} ${doc}) +endfunction() + # Set the default CMAKE_BUILD_TYPE to Release. # This should be done before the project command since the latter can set # CMAKE_BUILD_TYPE itself (it does so for nmake). if (MASTER_PROJECT AND NOT CMAKE_BUILD_TYPE) - join(doc "Choose the type of build, options are: None(CMAKE_CXX_FLAGS or " - "CMAKE_C_FLAGS used) Debug Release RelWithDebInfo MinSizeRel.") - set(CMAKE_BUILD_TYPE Release CACHE STRING ${doc}) + set_doc(CMAKE_BUILD_TYPE Release CACHE STRING + "Choose the type of build, options are: None(CMAKE_CXX_FLAGS or " + "CMAKE_C_FLAGS used) Debug Release RelWithDebInfo MinSizeRel.") endif () option(FMT_PEDANTIC "Enable extra warnings and expensive tests." OFF) @@ -131,7 +142,9 @@ if (MASTER_PROJECT AND CMAKE_GENERATOR MATCHES "Visual Studio") set(MSBUILD_SETUP "call \"${WINSDK_SETENV}\"") endif () # Set FrameworkPathOverride to get rid of MSB3644 warnings. - set(netfxpath "C:\\Program Files\\Reference Assemblies\\Microsoft\\Framework\\.NETFramework\\v4.0") + join(netfxpath + "C:\\Program Files\\Reference Assemblies\\Microsoft\\Framework\\" + ".NETFramework\\v4.0") file(WRITE run-msbuild.bat " ${MSBUILD_SETUP} ${CMAKE_MAKE_PROGRAM} -p:FrameworkPathOverride=\"${netfxpath}\" %*") @@ -210,7 +223,6 @@ add_library(fmt-header-only INTERFACE) add_library(fmt::fmt-header-only ALIAS fmt-header-only) target_compile_definitions(fmt-header-only INTERFACE FMT_HEADER_ONLY=1) - target_compile_features(fmt-header-only INTERFACE ${FMT_REQUIRED_FEATURES}) target_include_directories(fmt-header-only INTERFACE @@ -221,8 +233,9 @@ target_include_directories(fmt-header-only INTERFACE if (FMT_INSTALL) include(GNUInstallDirs) include(CMakePackageConfigHelpers) - set(FMT_CMAKE_DIR ${CMAKE_INSTALL_LIBDIR}/cmake/fmt CACHE STRING - "Installation directory for cmake files, relative to ${CMAKE_INSTALL_PREFIX}.") + set_doc(FMT_CMAKE_DIR ${CMAKE_INSTALL_LIBDIR}/cmake/fmt CACHE STRING + "Installation directory for cmake files, relative to " + "${CMAKE_INSTALL_PREFIX}.") set(version_config ${PROJECT_BINARY_DIR}/fmt-config-version.cmake) set(project_config ${PROJECT_BINARY_DIR}/fmt-config.cmake) set(pkgconfig ${PROJECT_BINARY_DIR}/fmt.pc) @@ -233,14 +246,17 @@ if (FMT_INSTALL) set(INSTALL_TARGETS ${INSTALL_TARGETS} fmt-header-only) endif () - set(FMT_LIB_DIR ${CMAKE_INSTALL_LIBDIR} CACHE STRING - "Installation directory for libraries, relative to ${CMAKE_INSTALL_PREFIX}.") + set_doc(FMT_LIB_DIR ${CMAKE_INSTALL_LIBDIR} CACHE STRING + "Installation directory for libraries, relative to " + "${CMAKE_INSTALL_PREFIX}.") - set(FMT_INC_DIR ${CMAKE_INSTALL_INCLUDEDIR}/fmt CACHE STRING - "Installation directory for include files, relative to ${CMAKE_INSTALL_PREFIX}.") + set_doc(FMT_INC_DIR ${CMAKE_INSTALL_INCLUDEDIR}/fmt CACHE STRINGS + "Installation directory for include files, relative to " + "${CMAKE_INSTALL_PREFIX}.") - set(FMT_PKGCONFIG_DIR ${CMAKE_INSTALL_LIBDIR}/pkgconfig CACHE PATH - "Installation directory for pkgconfig (.pc) files, relative to ${CMAKE_INSTALL_PREFIX}.") + set_doc(FMT_PKGCONFIG_DIR ${CMAKE_INSTALL_LIBDIR}/pkgconfig CACHE PATH + "Installation directory for pkgconfig (.pc) files, relative to " + "${CMAKE_INSTALL_PREFIX}.") # Generate the version, config and target files into the build directory. write_basic_package_version_file( @@ -297,7 +313,7 @@ set(gitignore ${PROJECT_SOURCE_DIR}/.gitignore) if (MASTER_PROJECT AND EXISTS ${gitignore}) # Get the list of ignored files from .gitignore. file (STRINGS ${gitignore} lines) - LIST(REMOVE_ITEM lines /doc/html) + list(REMOVE_ITEM lines /doc/html) foreach (line ${lines}) string(REPLACE "." "[.]" line "${line}") string(REPLACE "*" ".*" line "${line}") From 48e8d0ebef61c951568a65f9198be814aeadcdbb Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Sat, 7 Mar 2020 14:31:39 -0800 Subject: [PATCH 27/45] set_doc -> set_verbose --- CMakeLists.txt | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 11169ce7..fb75602e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,21 +27,21 @@ endfunction() # Sets a cache variable with a docstring joined from multiple arguments: # set( ... CACHE ...) # This allows splitting a long docstring for readability. -function(set_doc) - cmake_parse_arguments(SET_DOC "" "" "CACHE" ${ARGN}) - list(GET SET_DOC_CACHE 0 type) - list(REMOVE_AT SET_DOC_CACHE 0) - join(doc ${SET_DOC_CACHE}) - set(${SET_DOC_UNPARSED_ARGUMENTS} CACHE ${type} ${doc}) +function(set_verbose) + cmake_parse_arguments(SET_VERBOSE "" "" "CACHE" ${ARGN}) + list(GET SET_VERBOSE_CACHE 0 type) + list(REMOVE_AT SET_VERBOSE_CACHE 0) + join(doc ${SET_VERBOSE_CACHE}) + set(${SET_VERBOSE_UNPARSED_ARGUMENTS} CACHE ${type} ${doc}) endfunction() # Set the default CMAKE_BUILD_TYPE to Release. # This should be done before the project command since the latter can set # CMAKE_BUILD_TYPE itself (it does so for nmake). if (MASTER_PROJECT AND NOT CMAKE_BUILD_TYPE) - set_doc(CMAKE_BUILD_TYPE Release CACHE STRING - "Choose the type of build, options are: None(CMAKE_CXX_FLAGS or " - "CMAKE_C_FLAGS used) Debug Release RelWithDebInfo MinSizeRel.") + set_verbose(CMAKE_BUILD_TYPE Release CACHE STRING + "Choose the type of build, options are: None(CMAKE_CXX_FLAGS or " + "CMAKE_C_FLAGS used) Debug Release RelWithDebInfo MinSizeRel.") endif () option(FMT_PEDANTIC "Enable extra warnings and expensive tests." OFF) @@ -233,9 +233,9 @@ target_include_directories(fmt-header-only INTERFACE if (FMT_INSTALL) include(GNUInstallDirs) include(CMakePackageConfigHelpers) - set_doc(FMT_CMAKE_DIR ${CMAKE_INSTALL_LIBDIR}/cmake/fmt CACHE STRING - "Installation directory for cmake files, relative to " - "${CMAKE_INSTALL_PREFIX}.") + set_verbose(FMT_CMAKE_DIR ${CMAKE_INSTALL_LIBDIR}/cmake/fmt CACHE STRING + "Installation directory for cmake files, relative to " + "${CMAKE_INSTALL_PREFIX}.") set(version_config ${PROJECT_BINARY_DIR}/fmt-config-version.cmake) set(project_config ${PROJECT_BINARY_DIR}/fmt-config.cmake) set(pkgconfig ${PROJECT_BINARY_DIR}/fmt.pc) @@ -246,17 +246,17 @@ if (FMT_INSTALL) set(INSTALL_TARGETS ${INSTALL_TARGETS} fmt-header-only) endif () - set_doc(FMT_LIB_DIR ${CMAKE_INSTALL_LIBDIR} CACHE STRING - "Installation directory for libraries, relative to " - "${CMAKE_INSTALL_PREFIX}.") + set_verbose(FMT_LIB_DIR ${CMAKE_INSTALL_LIBDIR} CACHE STRING + "Installation directory for libraries, relative to " + "${CMAKE_INSTALL_PREFIX}.") - set_doc(FMT_INC_DIR ${CMAKE_INSTALL_INCLUDEDIR}/fmt CACHE STRINGS - "Installation directory for include files, relative to " - "${CMAKE_INSTALL_PREFIX}.") + set_verbose(FMT_INC_DIR ${CMAKE_INSTALL_INCLUDEDIR}/fmt CACHE STRINGS + "Installation directory for include files, relative to " + "${CMAKE_INSTALL_PREFIX}.") - set_doc(FMT_PKGCONFIG_DIR ${CMAKE_INSTALL_LIBDIR}/pkgconfig CACHE PATH - "Installation directory for pkgconfig (.pc) files, relative to " - "${CMAKE_INSTALL_PREFIX}.") + set_verbose(FMT_PKGCONFIG_DIR ${CMAKE_INSTALL_LIBDIR}/pkgconfig CACHE PATH + "Installation directory for pkgconfig (.pc) files, relative to " + "${CMAKE_INSTALL_PREFIX}.") # Generate the version, config and target files into the build directory. write_basic_package_version_file( From b1adaa9881a15f9777e4bc725b477eef26bedbf6 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Sat, 7 Mar 2020 14:50:52 -0800 Subject: [PATCH 28/45] Remove gcc 4.4 workaround --- support/cmake/cxx14.cmake | 11 ----------- test/CMakeLists.txt | 4 +--- 2 files changed, 1 insertion(+), 14 deletions(-) diff --git a/support/cmake/cxx14.cmake b/support/cmake/cxx14.cmake index 032fcb27..16ff5754 100644 --- a/support/cmake/cxx14.cmake +++ b/support/cmake/cxx14.cmake @@ -48,17 +48,6 @@ endif () set(CMAKE_REQUIRED_FLAGS ${CXX_STANDARD_FLAG}) -# Check if variadic templates are working and not affected by GCC bug 39653: -# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=39653 -# Can be removed once gcc 4.4 support is dropped. -check_cxx_source_compiles(" - template - struct S { typedef typename S::type type; }; - int main() {}" SUPPORTS_VARIADIC_TEMPLATES) -if (NOT SUPPORTS_VARIADIC_TEMPLATES) - set (SUPPORTS_VARIADIC_TEMPLATES OFF) -endif () - # Check if user-defined literals are available check_cxx_source_compiles(" void operator\"\" _udl(long double); diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index ad391c22..89176633 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -17,9 +17,7 @@ else () target_compile_definitions(gmock PUBLIC GTEST_HAS_PTHREAD=0) endif () -if (NOT SUPPORTS_VARIADIC_TEMPLATES) - target_compile_definitions(gmock PUBLIC GTEST_LANG_CXX11=0) -endif () +target_compile_definitions(gmock PUBLIC GTEST_LANG_CXX11=0) if (MSVC) # Workaround a bug in implementation of variadic templates in MSVC11. From 1c0c59d4a0da7915103fed64e0020306b4277904 Mon Sep 17 00:00:00 2001 From: Laurent Stacul Date: Mon, 9 Mar 2020 06:14:29 +0000 Subject: [PATCH 29/45] Fix empty debug postfix --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index fb75602e..b0928961 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -199,7 +199,7 @@ set(FMT_DEBUG_POSTFIX d CACHE STRING "Debug library postfix.") set_target_properties(fmt PROPERTIES VERSION ${FMT_VERSION} SOVERSION ${CPACK_PACKAGE_VERSION_MAJOR} - DEBUG_POSTFIX ${FMT_DEBUG_POSTFIX}) + DEBUG_POSTFIX "${FMT_DEBUG_POSTFIX}") # Set FMT_LIB_NAME for pkg-config fmt.pc. get_target_property(FMT_LIB_NAME fmt OUTPUT_NAME) From 5bb88566559b581dccf6728e810a2fa23fada328 Mon Sep 17 00:00:00 2001 From: Pramod Kumbhar Date: Mon, 9 Mar 2020 19:25:38 +0100 Subject: [PATCH 30/45] Workaround for broken [[deprecated]] in PGI compiler (#1581) * Workaround broken [[deprecated]] in PGI compiler - similar to Intel and NVCC, add workaround for PGI compiler --- include/fmt/core.h | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/include/fmt/core.h b/include/fmt/core.h index 0e6422d9..5496d895 100644 --- a/include/fmt/core.h +++ b/include/fmt/core.h @@ -147,8 +147,8 @@ # endif #endif -// Workaround broken [[deprecated]] in the Intel compiler and NVCC. -#if defined(__INTEL_COMPILER) || FMT_NVCC +// Workaround broken [[deprecated]] in the Intel, PGI and NVCC compiler +#if defined(__INTEL_COMPILER) || defined(__PGI) || FMT_NVCC # define FMT_DEPRECATED_ALIAS #else # define FMT_DEPRECATED_ALIAS FMT_DEPRECATED @@ -259,7 +259,8 @@ namespace internal { // A workaround for gcc 4.8 to make void_t work in a SFINAE context. template struct void_t_impl { using type = void; }; -FMT_NORETURN FMT_API void assert_fail(const char* file, int line, const char* message); +FMT_NORETURN FMT_API void assert_fail(const char* file, int line, + const char* message); #ifndef FMT_ASSERT # ifdef NDEBUG From ee2b828b9a04ef6b9595e779a7878d4d27018cee Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Mon, 9 Mar 2020 11:27:14 -0700 Subject: [PATCH 31/45] Tweak a comment --- include/fmt/core.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/fmt/core.h b/include/fmt/core.h index 5496d895..72bbddad 100644 --- a/include/fmt/core.h +++ b/include/fmt/core.h @@ -147,7 +147,7 @@ # endif #endif -// Workaround broken [[deprecated]] in the Intel, PGI and NVCC compiler +// Workaround broken [[deprecated]] in the Intel, PGI and NVCC compilers. #if defined(__INTEL_COMPILER) || defined(__PGI) || FMT_NVCC # define FMT_DEPRECATED_ALIAS #else From 941d5e147a2875226a25877196dab32ac629a67d Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Wed, 11 Mar 2020 07:56:18 -0700 Subject: [PATCH 32/45] Workaround broken fallthrough attribute in the PGI compiler (#1583) --- include/fmt/format.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/fmt/format.h b/include/fmt/format.h index 16b77fa5..537a80ce 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -66,7 +66,7 @@ #if __cplusplus == 201103L || __cplusplus == 201402L # if defined(__clang__) # define FMT_FALLTHROUGH [[clang::fallthrough]] -# elif FMT_GCC_VERSION >= 700 +# elif FMT_GCC_VERSION >= 700 && !defined(__PGI) # define FMT_FALLTHROUGH [[gnu::fallthrough]] # else # define FMT_FALLTHROUGH From f72a905eb3f003ccc13596daff223a3f2b494164 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Wed, 11 Mar 2020 08:40:57 -0700 Subject: [PATCH 33/45] Fix handling of volatile enums --- include/fmt/printf.h | 5 +++++ test/printf-test.cc | 12 +++++++++--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/include/fmt/printf.h b/include/fmt/printf.h index 8a2a8c20..a7902280 100644 --- a/include/fmt/printf.h +++ b/include/fmt/printf.h @@ -303,6 +303,8 @@ class printf_arg_formatter : public internal::arg_formatter_base { }; template struct printf_formatter { + printf_formatter() = delete; + template auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { return ctx.begin(); @@ -320,6 +322,7 @@ template class basic_printf_context { public: /** The character type for the output. */ using char_type = Char; + using iterator = OutputIt; using format_arg = basic_format_arg; template using formatter_type = printf_formatter; @@ -355,6 +358,8 @@ template class basic_printf_context { OutputIt out() { return out_; } void advance_to(OutputIt it) { out_ = it; } + internal::locale_ref locale() { return {}; } + format_arg arg(int id) const { return args_.get(id); } basic_format_parse_context& parse_context() { return parse_ctx_; } diff --git a/test/printf-test.cc b/test/printf-test.cc index f591ea61..5aaa27b1 100644 --- a/test/printf-test.cc +++ b/test/printf-test.cc @@ -474,9 +474,13 @@ TEST(PrintfTest, Location) { // TODO: test %n } -enum E { A = 42 }; +enum test_enum { answer = 42 }; -TEST(PrintfTest, Enum) { EXPECT_PRINTF("42", "%d", A); } +TEST(PrintfTest, Enum) { + EXPECT_PRINTF("42", "%d", answer); + volatile test_enum volatile_enum = answer; + EXPECT_PRINTF("42", "%d", volatile_enum); +} #if FMT_USE_FCNTL TEST(PrintfTest, Examples) { @@ -498,7 +502,9 @@ TEST(PrintfTest, PrintfError) { TEST(PrintfTest, WideString) { EXPECT_EQ(L"abc", fmt::sprintf(L"%s", L"abc")); } TEST(PrintfTest, PrintfCustom) { - EXPECT_EQ("abc", test_sprintf("%s", TestString("abc"))); + // The test is disabled for now because it requires decoupling + // fallback_formatter::format from format_context. + //EXPECT_EQ("abc", test_sprintf("%s", TestString("abc"))); } TEST(PrintfTest, OStream) { From 3c24052cf17e23a285316ac52178935599ad6a47 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Wed, 11 Mar 2020 17:29:07 -0700 Subject: [PATCH 34/45] Workaround 'cannot call member function without object' error on gcc 4.9 --- include/fmt/core.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/include/fmt/core.h b/include/fmt/core.h index 72bbddad..8bcc0d06 100644 --- a/include/fmt/core.h +++ b/include/fmt/core.h @@ -950,8 +950,9 @@ template struct arg_mapper { FMT_ENABLE_IF(std::is_enum::value && !has_formatter::value && !has_fallback_formatter::value)> - FMT_CONSTEXPR auto map(const T& val) -> decltype( - map(static_cast::type>(val))) { + FMT_CONSTEXPR auto map(const T& val) + -> decltype(std::declval().map( + static_cast::type>(val))) { return map(static_cast::type>(val)); } template Date: Mon, 2 Mar 2020 14:51:51 +0000 Subject: [PATCH 35/45] Add FMT_HAS_CPP14_ATTRIBUTE / FMT_HAS_CPP17_ATTRIBUTE to test for language-specific attributes. FMT_DEPRECATED is now defined as FMT_HAS_CPP14_ATTRIBUTE(deprecated), as this attribute was introduced in C++14. FMT_FALLTHROUGH is now defined as FMT_HAS_CPP17_ATTRIBUTE(fallthrough), as this attribute was introduced in C++17. FMT_MAYBE_UNUSED is defined as FMT_HAS_CPP17_ATTRIBUTE(maybe_unused), as this attribute was introduced in C++17. FMT_MAYBE_UNUSED has been applied to fix a couple of -Wunused-member-function warnings from clang. --- include/fmt/core.h | 17 +++++++++++++++-- include/fmt/format-inl.h | 3 +++ include/fmt/format.h | 4 ++-- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/include/fmt/core.h b/include/fmt/core.h index 8bcc0d06..25647037 100644 --- a/include/fmt/core.h +++ b/include/fmt/core.h @@ -36,6 +36,12 @@ # define FMT_HAS_CPP_ATTRIBUTE(x) 0 #endif +#define FMT_HAS_CPP14_ATTRIBUTE(attribute) \ + (__cplusplus >= 201402L && FMT_HAS_CPP_ATTRIBUTE(attribute)) + +#define FMT_HAS_CPP17_ATTRIBUTE(attribute) \ + (__cplusplus >= 201703L && FMT_HAS_CPP_ATTRIBUTE(attribute)) + #ifdef __clang__ # define FMT_CLANG_VERSION (__clang_major__ * 100 + __clang_minor__) #else @@ -132,9 +138,16 @@ # define FMT_NORETURN #endif +#ifndef FMT_MAYBE_UNUSED +# if FMT_HAS_CPP17_ATTRIBUTE(maybe_unused) +# define FMT_MAYBE_UNUSED [[maybe_unused]] +# else +# define FMT_MAYBE_UNUSED +# endif +#endif + #ifndef FMT_DEPRECATED -# if (FMT_HAS_CPP_ATTRIBUTE(deprecated) && __cplusplus >= 201402L) || \ - FMT_MSC_VER >= 1900 +# if FMT_HAS_CPP14_ATTRIBUTE(deprecated) || FMT_MSC_VER >= 1900 # define FMT_DEPRECATED [[deprecated]] # else # if defined(__GNUC__) || defined(__clang__) diff --git a/include/fmt/format-inl.h b/include/fmt/format-inl.h index c42f7280..5d294bc7 100644 --- a/include/fmt/format-inl.h +++ b/include/fmt/format-inl.h @@ -86,6 +86,7 @@ FMT_FUNC int safe_strerror(int error_code, char*& buffer, } // Handle the result of GNU-specific version of strerror_r. + FMT_MAYBE_UNUSED int handle(char* message) { // If the buffer is full then the message is probably truncated. if (message == buffer_ && strlen(buffer_) == buffer_size_ - 1) @@ -95,11 +96,13 @@ FMT_FUNC int safe_strerror(int error_code, char*& buffer, } // Handle the case when strerror_r is not available. + FMT_MAYBE_UNUSED int handle(internal::null<>) { return fallback(strerror_s(buffer_, buffer_size_, error_code_)); } // Fallback to strerror_s when strerror_r is not available. + FMT_MAYBE_UNUSED int fallback(int result) { // If the buffer is full then the message is probably truncated. return result == 0 && strlen(buffer_) == buffer_size_ - 1 ? ERANGE diff --git a/include/fmt/format.h b/include/fmt/format.h index 537a80ce..07d4f5f8 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -71,7 +71,7 @@ # else # define FMT_FALLTHROUGH # endif -#elif (FMT_HAS_CPP_ATTRIBUTE(fallthrough) && (__cplusplus >= 201703)) || \ +#elif FMT_HAS_CPP17_ATTRIBUTE(fallthrough) || \ (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) # define FMT_FALLTHROUGH [[fallthrough]] #else @@ -3549,7 +3549,7 @@ FMT_END_NAMESPACE /* Use a macro-like name to avoid shadowing warnings. */ \ struct FMT_COMPILE_STRING : fmt::compile_string { \ using char_type = fmt::remove_cvref_t; \ - __VA_ARGS__ FMT_CONSTEXPR \ + FMT_MAYBE_UNUSED __VA_ARGS__ FMT_CONSTEXPR \ operator fmt::basic_string_view() const { \ /* FMT_STRING only accepts string literals. */ \ return fmt::internal::literal_to_view(s); \ From 61c5a516048854eb044e47cabe032a092128edc2 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Sat, 14 Mar 2020 07:41:08 -0700 Subject: [PATCH 36/45] Fix handling of empty tuples (#1588) --- include/fmt/ranges.h | 6 ++---- test/ranges-test.cc | 8 +++++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/include/fmt/ranges.h b/include/fmt/ranges.h index 6110fdaf..a789a975 100644 --- a/include/fmt/ranges.h +++ b/include/fmt/ranges.h @@ -13,6 +13,7 @@ #define FMT_RANGES_H_ #include + #include "format.h" // output only up to N items from the range. @@ -104,10 +105,7 @@ struct is_range_< /// tuple_size and tuple_element check. template class is_tuple_like_ { template - static auto check(U* p) - -> decltype(std::tuple_size::value, - (void)std::declval::type>(), - int()); + static auto check(U* p) -> decltype(std::tuple_size::value, int()); template static void check(...); public: diff --git a/test/ranges-test.cc b/test/ranges-test.cc index 265f9acd..ee6c4551 100644 --- a/test/ranges-test.cc +++ b/test/ranges-test.cc @@ -10,6 +10,7 @@ // {fmt} support for ranges, containers and types tuple interface. #include "fmt/ranges.h" + #include "gtest.h" // Check if 'if constexpr' is supported. @@ -44,9 +45,10 @@ TEST(RangesTest, FormatPair) { } TEST(RangesTest, FormatTuple) { - std::tuple tu1{42, 1.5f, "this is tuple", - 'i'}; - EXPECT_EQ("(42, 1.5, \"this is tuple\", 'i')", fmt::format("{}", tu1)); + std::tuple t{42, 1.5f, "this is tuple", + 'i'}; + EXPECT_EQ("(42, 1.5, \"this is tuple\", 'i')", fmt::format("{}", t)); + EXPECT_EQ("()", fmt::format("{}", std::tuple<>())); } TEST(RangesTest, JoinTuple) { From 6f01b6ebb6c5cb45c1cd5def4dab5ab9db4d1cb6 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Sat, 14 Mar 2020 09:50:25 -0700 Subject: [PATCH 37/45] Fix a typo in CMake config: STRINGS -> STRING --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b0928961..466b342f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -250,7 +250,7 @@ if (FMT_INSTALL) "Installation directory for libraries, relative to " "${CMAKE_INSTALL_PREFIX}.") - set_verbose(FMT_INC_DIR ${CMAKE_INSTALL_INCLUDEDIR}/fmt CACHE STRINGS + set_verbose(FMT_INC_DIR ${CMAKE_INSTALL_INCLUDEDIR}/fmt CACHE STRING "Installation directory for include files, relative to " "${CMAKE_INSTALL_PREFIX}.") From 678341275b62689264ba0fe2a4c47f086874925c Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Sat, 14 Mar 2020 10:32:34 -0700 Subject: [PATCH 38/45] Deprecate fmt::char8_t --- include/fmt/core.h | 12 +++++++++--- include/fmt/format.h | 26 ++++++++++++++------------ test/format-impl-test.cc | 11 ++++++----- test/format-test.cc | 33 +++++++++++---------------------- 4 files changed, 40 insertions(+), 42 deletions(-) diff --git a/include/fmt/core.h b/include/fmt/core.h index 25647037..4734aba2 100644 --- a/include/fmt/core.h +++ b/include/fmt/core.h @@ -315,6 +315,12 @@ FMT_CONSTEXPR typename std::make_unsigned::type to_unsigned(Int value) { FMT_ASSERT(value >= 0, "negative value"); return static_cast::type>(value); } + +#ifdef __cpp_char8_t +using char8_type = char8_t; +#else +enum char8_type : unsigned char {}; +#endif } // namespace internal template @@ -415,15 +421,15 @@ using string_view = basic_string_view; using wstring_view = basic_string_view; #ifndef __cpp_char8_t -// A UTF-8 code unit type. -enum char8_t : unsigned char {}; +// char8_t is deprecated; use char instead. +using char8_t FMT_DEPRECATED_ALIAS = internal::char8_type; #endif /** Specifies if ``T`` is a character type. Can be specialized by users. */ template struct is_char : std::false_type {}; template <> struct is_char : std::true_type {}; template <> struct is_char : std::true_type {}; -template <> struct is_char : std::true_type {}; +template <> struct is_char : std::true_type {}; template <> struct is_char : std::true_type {}; template <> struct is_char : std::true_type {}; diff --git a/include/fmt/format.h b/include/fmt/format.h index 07d4f5f8..ee7f0f93 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -482,7 +482,7 @@ inline size_t count_code_points(basic_string_view s) { return num_code_points; } -inline size_t count_code_points(basic_string_view s) { +inline size_t count_code_points(basic_string_view s) { return count_code_points(basic_string_view( reinterpret_cast(s.data()), s.size())); } @@ -494,8 +494,8 @@ inline size_t code_point_index(basic_string_view s, size_t n) { } // Calculates the index of the nth code point in a UTF-8 string. -inline size_t code_point_index(basic_string_view s, size_t n) { - const char8_t* data = s.data(); +inline size_t code_point_index(basic_string_view s, size_t n) { + const char8_type* data = s.data(); size_t num_code_points = 0; for (size_t i = 0, size = s.size(); i != size; ++i) { if ((data[i] & 0xc0) != 0x80 && ++num_code_points > n) { @@ -505,13 +505,13 @@ inline size_t code_point_index(basic_string_view s, size_t n) { return s.size(); } -inline char8_t to_char8_t(char c) { return static_cast(c); } +inline char8_type to_char8_t(char c) { return static_cast(c); } template using needs_conversion = bool_constant< std::is_same::value_type, char>::value && - std::is_same::value>; + std::is_same::value>; template ::value)> @@ -555,20 +555,22 @@ class buffer_range : public internal::output_range< : internal::output_range(std::back_inserter(buf)) {} }; -class FMT_DEPRECATED u8string_view : public basic_string_view { +class FMT_DEPRECATED u8string_view + : public basic_string_view { public: u8string_view(const char* s) - : basic_string_view(reinterpret_cast(s)) {} + : basic_string_view( + reinterpret_cast(s)) {} u8string_view(const char* s, size_t count) FMT_NOEXCEPT - : basic_string_view(reinterpret_cast(s), count) { - } + : basic_string_view( + reinterpret_cast(s), count) {} }; #if FMT_USE_USER_DEFINED_LITERALS inline namespace literals { -FMT_DEPRECATED inline basic_string_view operator"" _u(const char* s, - std::size_t n) { - return {reinterpret_cast(s), n}; +FMT_DEPRECATED inline basic_string_view operator"" _u( + const char* s, std::size_t n) { + return {reinterpret_cast(s), n}; } } // namespace literals #endif diff --git a/test/format-impl-test.cc b/test/format-impl-test.cc index 34814967..21e91014 100644 --- a/test/format-impl-test.cc +++ b/test/format-impl-test.cc @@ -10,12 +10,11 @@ #include "test-assert.h" // Include format.cc instead of format.h to test implementation. -#include "../src/format.cc" -#include "fmt/printf.h" - #include #include +#include "../src/format.cc" +#include "fmt/printf.h" #include "gmock.h" #include "gtest-extra.h" #include "util.h" @@ -430,8 +429,10 @@ TEST(FormatTest, CountCodePoints) { #ifndef __cpp_char8_t using fmt::char8_t; #endif - EXPECT_EQ(4, fmt::internal::count_code_points( - fmt::basic_string_view(reinterpret_cast("ёжик")))); + EXPECT_EQ( + 4, fmt::internal::count_code_points( + fmt::basic_string_view( + reinterpret_cast("ёжик")))); } // Tests fmt::internal::count_digits for integer type Int. diff --git a/test/format-test.cc b/test/format-test.cc index 92ad5369..af72acaf 100644 --- a/test/format-test.cc +++ b/test/format-test.cc @@ -2504,25 +2504,6 @@ TEST(FormatTest, FmtStringInTemplate) { #endif // FMT_USE_CONSTEXPR -// C++20 feature test, since r346892 Clang considers char8_t a fundamental -// type in this mode. If this is the case __cpp_char8_t will be defined. -#ifndef __cpp_char8_t -// Locally provide type char8_t defined in format.h -using fmt::char8_t; -#endif - -// Convert 'char8_t' character sequences to 'char' sequences -// Otherwise GTest will insist on inserting 'char8_t' NTBS into a 'char' stream, -// but basic_ostream::operator<< overloads taking 'char8_t' arguments -// are defined as deleted by P1423. -// Handling individual 'char8_t's is done inline. -std::string from_u8str(const std::basic_string& str) { - return std::string(str.begin(), str.end()); -} -std::string from_u8str(const fmt::basic_string_view& str) { - return std::string(str.begin(), str.end()); -} - TEST(FormatTest, EmphasisNonHeaderOnly) { // Ensure this compiles even if FMT_HEADER_ONLY is not defined. EXPECT_EQ(fmt::format(fmt::emphasis::bold, "bold error"), @@ -2561,10 +2542,18 @@ TEST(FormatTest, FormatCustomChar) { EXPECT_EQ(result[0], mychar('x')); } +// Convert a char8_t string to std::string. Otherwise GTest will insist on +// inserting `char8_t` NTBS into a `char` stream which is disabled by P1423. +template std::string from_u8str(const S& str) { + return std::string(str.begin(), str.end()); +} + TEST(FormatTest, FormatUTF8Precision) { - using str_type = std::basic_string; - str_type format(reinterpret_cast(u8"{:.4}")); - str_type str(reinterpret_cast(u8"caf\u00e9s")); // cafés + using str_type = std::basic_string; + str_type format( + reinterpret_cast(u8"{:.4}")); + str_type str(reinterpret_cast( + u8"caf\u00e9s")); // cafés auto result = fmt::format(format, str); EXPECT_EQ(fmt::internal::count_code_points(result), 4); EXPECT_EQ(result.size(), 5); From ff486a72a764ff9f0ea3be460bd713ed7a237db6 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Sat, 14 Mar 2020 11:37:38 -0700 Subject: [PATCH 39/45] Allow leading zeros in precision (#1579) --- include/fmt/format.h | 10 +++++----- test/format-test.cc | 1 + 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/include/fmt/format.h b/include/fmt/format.h index ee7f0f93..a529383a 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -1953,10 +1953,6 @@ template FMT_CONSTEXPR int parse_nonnegative_int(const Char*& begin, const Char* end, ErrorHandler&& eh) { FMT_ASSERT(begin != end && '0' <= *begin && *begin <= '9', ""); - if (*begin == '0') { - ++begin; - return 0; - } unsigned value = 0; // Convert to unsigned to prevent a warning. constexpr unsigned max_int = max_value(); @@ -2311,7 +2307,11 @@ FMT_CONSTEXPR const Char* parse_arg_id(const Char* begin, const Char* end, return begin; } if (c >= '0' && c <= '9') { - int index = parse_nonnegative_int(begin, end, handler); + int index = 0; + if (c != '0') + index = parse_nonnegative_int(begin, end, handler); + else + ++begin; if (begin == end || (*begin != '}' && *begin != ':')) handler.on_error("invalid format string"); else diff --git a/test/format-test.cc b/test/format-test.cc index af72acaf..a2223749 100644 --- a/test/format-test.cc +++ b/test/format-test.cc @@ -1124,6 +1124,7 @@ TEST(FormatterTest, Precision) { "000000000000000000000000000000000000000000000000000P+127", format("{:.838A}", -2.14001164E+38)); EXPECT_EQ("123.", format("{:#.0f}", 123.0)); + EXPECT_EQ("1.23", format("{:.02f}", 1.234)); EXPECT_THROW_MSG(format("{0:.2}", reinterpret_cast(0xcafe)), format_error, From 85050aa2e61cfc3c29cd8b74c472df1dd8db588c Mon Sep 17 00:00:00 2001 From: Nikolay Rapotkin Date: Fri, 13 Mar 2020 11:20:24 +0300 Subject: [PATCH 40/45] Ability to join elements of std::initializer_list was added --- include/fmt/ranges.h | 24 ++++++++++++++++++++++++ test/ranges-test.cc | 6 ++++++ 2 files changed, 30 insertions(+) diff --git a/include/fmt/ranges.h b/include/fmt/ranges.h index a789a975..f8f9adb7 100644 --- a/include/fmt/ranges.h +++ b/include/fmt/ranges.h @@ -12,6 +12,7 @@ #ifndef FMT_RANGES_H_ #define FMT_RANGES_H_ +#include #include #include "format.h" @@ -358,6 +359,29 @@ FMT_CONSTEXPR tuple_arg_join join(const std::tuple& tuple, return {tuple, sep}; } +/** + \rst + Returns an object that formats `initializer_list` with elements separated by + `sep`. + + **Example**:: + + fmt::print("{}", fmt::join({1, 2, 3}, ", ")); + // Output: "1, 2, 3" + \endrst + */ +template +arg_join>, char> join( + std::initializer_list list, string_view sep) { + return join(std::begin(list), std::end(list), sep); +} + +template +arg_join>, wchar_t> join( + std::initializer_list list, wstring_view sep) { + return join(std::begin(list), std::end(list), sep); +} + FMT_END_NAMESPACE #endif // FMT_RANGES_H_ diff --git a/test/ranges-test.cc b/test/ranges-test.cc index ee6c4551..a729948d 100644 --- a/test/ranges-test.cc +++ b/test/ranges-test.cc @@ -70,6 +70,12 @@ TEST(RangesTest, JoinTuple) { EXPECT_EQ("4.0", fmt::format("{}", fmt::join(t4, "/"))); } +TEST(RangesTest, JoinInitializerList) { + EXPECT_EQ("1, 2, 3", fmt::format("{}", fmt::join({1, 2, 3}, ", "))); + EXPECT_EQ("fmt rocks !", + fmt::format("{}", fmt::join({"fmt", "rocks", "!"}, " "))); +} + struct my_struct { int32_t i; std::string str; // can throw From 6012dc9ab415c7a873e1a1abd06fcbbef13e5581 Mon Sep 17 00:00:00 2001 From: Vladimir Solontsov <56200572+vsolontsov-ll@users.noreply.github.com> Date: Mon, 16 Mar 2020 17:00:29 +0300 Subject: [PATCH 41/45] Dynamic arguments storage. Implementation of enhancement from issue #1170. (#1584) --- include/fmt/core.h | 194 +++++++++++++++++++++++++++++++++++ include/fmt/format.h | 4 - test/CMakeLists.txt | 1 + test/format-dyn-args-test.cc | 87 ++++++++++++++++ 4 files changed, 282 insertions(+), 4 deletions(-) create mode 100644 test/format-dyn-args-test.cc diff --git a/include/fmt/core.h b/include/fmt/core.h index 4734aba2..227d15f7 100644 --- a/include/fmt/core.h +++ b/include/fmt/core.h @@ -10,9 +10,12 @@ #include // std::FILE #include +#include #include +#include #include #include +#include // The fmt library version in the form major * 10000 + minor * 100 + patch. #define FMT_VERSION 60103 @@ -269,6 +272,10 @@ struct monostate {}; namespace internal { +// A helper function to suppress bogus "conditional expression is constant" +// warnings. +template FMT_CONSTEXPR T const_check(T value) { return value; } + // A workaround for gcc 4.8 to make void_t work in a SFINAE context. template struct void_t_impl { using type = void; }; @@ -1307,6 +1314,8 @@ inline format_arg_store make_format_args( return {args...}; } +template class dynamic_format_arg_store; + /** \rst A view of a collection of formatting arguments. To avoid lifetime issues it @@ -1378,6 +1387,17 @@ template class basic_format_args { set_data(store.data_); } + /** + \rst + Constructs a `basic_format_args` object from + `~fmt::dynamic_format_arg_store`. + \endrst + */ + basic_format_args(const dynamic_format_arg_store& store) + : types_(store.get_types()) { + set_data(store.data_.data()); + } + /** \rst Constructs a `basic_format_args` object from a dynamic set of arguments. @@ -1619,6 +1639,180 @@ inline void print(const S& format_str, Args&&... args) { internal::make_args_checked(format_str, args...)); #endif } + +namespace internal { + +template struct is_string_view : std::false_type {}; + +template +struct is_string_view, Char> : std::true_type {}; + +template +struct is_string_view, Char> : std::true_type {}; + +template struct is_reference_wrapper : std::false_type {}; + +template +struct is_reference_wrapper> : std::true_type {}; + +class dyn_arg_storage { + // Workaround for clang's -Wweak-vtables. Unlike for regular classes, for + // templates it doesn't complain about inability to deduce single translation + // unit for placing vtable. So storage_node_base is made a fake template. + + template struct storage_node_base { + using owning_ptr = std::unique_ptr>; + virtual ~storage_node_base() = default; + owning_ptr next; + }; + + template struct storage_node : storage_node_base<> { + T value; + template + FMT_CONSTEXPR storage_node(const Arg& arg, owning_ptr&& next) : value{arg} { + // Must be initialised after value_ + this->next = std::move(next); + } + + template + FMT_CONSTEXPR storage_node(const basic_string_view& arg, + owning_ptr&& next) + : value{arg.data(), arg.size()} { + // Must be initialised after value + this->next = std::move(next); + } + }; + + storage_node_base<>::owning_ptr head_{nullptr}; + + public: + dyn_arg_storage() = default; + dyn_arg_storage(const dyn_arg_storage&) = delete; + dyn_arg_storage(dyn_arg_storage&&) = default; + + dyn_arg_storage& operator=(const dyn_arg_storage&) = delete; + dyn_arg_storage& operator=(dyn_arg_storage&&) = default; + + template const T& push(const Arg& arg) { + auto node = new storage_node{arg, std::move(head_)}; + head_.reset(node); + return node->value; + } +}; + +} // namespace internal + +/** + \rst + A dynamic version of `fmt::format_arg_store<>`. + It's equipped with a storage to potentially temporary objects which lifetime + could be shorter than the format arguments object. + + It can be implicitly converted into `~fmt::basic_format_args` for passing + into type-erased formatting functions such as `~fmt::vformat`. + \endrst + */ +template +class dynamic_format_arg_store +#if FMT_GCC_VERSION < 409 + // Workaround a GCC template argument substitution bug. + : public basic_format_args +#endif +{ + private: + using char_type = typename Context::char_type; + + template struct need_dyn_copy { + static constexpr internal::type mapped_type = + internal::mapped_type_constant::value; +// static_assert( +// mapped_type != internal::type::named_arg_type, +// "Bug indicator. Named arguments must be processed separately"); + + using type = std::integral_constant< + bool, !(internal::is_reference_wrapper::value || + internal::is_string_view::value || + (mapped_type != internal::type::cstring_type && + mapped_type != internal::type::string_type && + mapped_type != internal::type::custom_type && + mapped_type != internal::type::named_arg_type))>; + }; + + template + using stored_type = conditional_t::value, + std::basic_string, T>; + + // Storage of basic_format_arg must be contiguous + // Required by basic_format_args::args_ which is just a pointer. + std::vector> data_; + + // Storage of arguments not fitting into basic_format_arg must grow + // without relocation because items in data_ refer to it. + + internal::dyn_arg_storage storage_; + + friend class basic_format_args; + + unsigned long long get_types() const { + return internal::is_unpacked_bit | data_.size(); + } + + template void emplace_arg(const T& arg) { + data_.emplace_back(internal::make_arg(arg)); + } + + public: + dynamic_format_arg_store() = default; + ~dynamic_format_arg_store() = default; + + dynamic_format_arg_store(const dynamic_format_arg_store&) = delete; + dynamic_format_arg_store& operator=(const dynamic_format_arg_store&) = delete; + + dynamic_format_arg_store(dynamic_format_arg_store&&) = default; + dynamic_format_arg_store& operator=(dynamic_format_arg_store&&) = default; + + /** + \rst + Adds an argument into the dynamic store for later passing to a formating + function. + + Note that custom types and string types (but not string views!) are copied + into the store with dynamic memory (in addition to resizing vector). + + **Example**:: + + #include + fmt::dynamic_format_arg_store store; + store.push_back(42); + store.push_back("abc1"); + store.push_back(1.5f); + std::string result = fmt::vformat("{} and {} and {}", store); + \endrst + */ + template void push_back(const T& arg) { + static_assert( + !std::is_base_of, T>::value, + "Named arguments are not supported yet"); + if (internal::const_check(need_dyn_copy::type::value)) + emplace_arg(storage_.push>(arg)); + else + emplace_arg(arg); + } + + /** + \rst + Adds an argument into the dynamic store for later passing to a formating + function without copying into type-erasing list. + \endrst + */ + template void push_back(std::reference_wrapper arg) { + static_assert( + need_dyn_copy::type::value, + "Primitive types and string views directly supported by " + "basic_format_arg. Passing them by reference is not allowed"); + emplace_arg(arg.get()); + } +}; FMT_END_NAMESPACE #endif // FMT_CORE_H_ diff --git a/include/fmt/format.h b/include/fmt/format.h index a529383a..36c30b9f 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -213,10 +213,6 @@ FMT_END_NAMESPACE FMT_BEGIN_NAMESPACE namespace internal { -// A helper function to suppress bogus "conditional expression is constant" -// warnings. -template FMT_CONSTEXPR T const_check(T value) { return value; } - // An equivalent of `*reinterpret_cast(&source)` that doesn't have // undefined behavior (e.g. due to type aliasing). // Example: uint64_t d = bit_cast(2.718); diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 89176633..23ea9fcf 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -95,6 +95,7 @@ add_fmt_test(grisu-test) target_compile_definitions(grisu-test PRIVATE FMT_USE_GRISU=1) add_fmt_test(gtest-extra-test) add_fmt_test(format-test mock-allocator.h) +add_fmt_test(format-dyn-args-test) if (MSVC) target_compile_options(format-test PRIVATE /bigobj) endif () diff --git a/test/format-dyn-args-test.cc b/test/format-dyn-args-test.cc new file mode 100644 index 00000000..da4da7ff --- /dev/null +++ b/test/format-dyn-args-test.cc @@ -0,0 +1,87 @@ +// Copyright (c) 2020 Vladimir Solontsov +// SPDX-License-Identifier: MIT Licence + +#include + +#include "gtest-extra.h" + +TEST(FormatDynArgsTest, Basic) { + fmt::dynamic_format_arg_store store; + store.push_back(42); + store.push_back("abc1"); + store.push_back(1.5f); + + std::string result = fmt::vformat("{} and {} and {}", store); + + EXPECT_EQ("42 and abc1 and 1.5", result); +} + +TEST(FormatDynArgsTest, StringsAndRefs) { + // Unfortunately the tests are compiled with old ABI + // So strings use COW. + fmt::dynamic_format_arg_store store; + char str[]{"1234567890"}; + store.push_back(str); + store.push_back(std::cref(str)); + store.push_back(fmt::string_view{str}); + str[0] = 'X'; + + std::string result = fmt::vformat("{} and {} and {}", store); + + EXPECT_EQ("1234567890 and X234567890 and X234567890", result); +} + +struct custom_type { + int i{0}; +}; +FMT_BEGIN_NAMESPACE + +template <> struct formatter { + auto parse(format_parse_context& ctx) const -> decltype(ctx.begin()) { + return ctx.begin(); + } + + template + auto format(const custom_type& p, FormatContext& ctx) -> decltype(format_to( + ctx.out(), std::declval())) { + return format_to(ctx.out(), "cust={}", p.i); + } +}; +FMT_END_NAMESPACE + +TEST(FormatDynArgsTest, CustomFormat) { + fmt::dynamic_format_arg_store store; + custom_type c{}; + store.push_back(c); + ++c.i; + store.push_back(c); + ++c.i; + store.push_back(std::cref(c)); + ++c.i; + + std::string result = fmt::vformat("{} and {} and {}", store); + + EXPECT_EQ("cust=0 and cust=1 and cust=3", result); +} + +TEST(FormatDynArgsTest, NamedArgByRef) { + fmt::dynamic_format_arg_store store; + + // Note: fmt::arg() constructs an object which holds a reference + // to its value. It's not an aggregate, so it doesn't extend the + // reference lifetime. As a result, it's a very bad idea passing temporary + // as a named argument value. Only GCC with optimization level >0 + // complains about this. + // + // A real life usecase is when you have both name and value alive + // guarantee their lifetime and thus don't want them to be copied into + // storages. + int a1_val{42}; + auto a1 = fmt::arg("a1_", a1_val); + store.push_back(std::cref(a1)); + + std::string result = fmt::vformat("{a1_}", // and {} and {}", + store); + + EXPECT_EQ("42", result); +} From 9f70fc3e7a1725c50de294f8f966789bf6796099 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Mon, 16 Mar 2020 07:51:57 -0700 Subject: [PATCH 42/45] Minor tweaks for dynamic_format_arg_store --- include/fmt/core.h | 333 +++++++++++++++++------------------ test/CMakeLists.txt | 1 - test/core-test.cc | 80 ++++++++- test/format-dyn-args-test.cc | 81 --------- 4 files changed, 236 insertions(+), 259 deletions(-) diff --git a/include/fmt/core.h b/include/fmt/core.h index 227d15f7..8dc770b4 100644 --- a/include/fmt/core.h +++ b/include/fmt/core.h @@ -1205,6 +1205,62 @@ template make_arg(const T& value) { return make_arg(value); } + +template struct is_string_view : std::false_type {}; + +template +struct is_string_view, Char> : std::true_type {}; + +template +struct is_string_view, Char> : std::true_type {}; + +template struct is_reference_wrapper : std::false_type {}; + +template +struct is_reference_wrapper> : std::true_type {}; + +class dyn_arg_storage { + // Workaround for clang's -Wweak-vtables. Unlike for regular classes, for + // templates it doesn't complain about inability to deduce single translation + // unit for placing vtable. So storage_node_base is made a fake template. + template struct storage_node_base { + using owning_ptr = std::unique_ptr>; + virtual ~storage_node_base() = default; + owning_ptr next; + }; + + template struct storage_node : storage_node_base<> { + T value; + + template + FMT_CONSTEXPR storage_node(const Arg& arg, owning_ptr&& next) : value(arg) { + this->next = std::move(next); // Must be initialised after value. + } + + template + FMT_CONSTEXPR storage_node(const basic_string_view& arg, + owning_ptr&& next) + : value(arg.data(), arg.size()) { + this->next = std::move(next); // Must be initialised after value. + } + }; + + storage_node_base<>::owning_ptr head_{nullptr}; + + public: + dyn_arg_storage() = default; + dyn_arg_storage(const dyn_arg_storage&) = delete; + dyn_arg_storage(dyn_arg_storage&&) = default; + + dyn_arg_storage& operator=(const dyn_arg_storage&) = delete; + dyn_arg_storage& operator=(dyn_arg_storage&&) = default; + + template const T& push(const Arg& arg) { + auto node = new storage_node(arg, std::move(head_)); + head_.reset(node); + return node->value; + } +}; } // namespace internal // Formatting context. @@ -1314,7 +1370,108 @@ inline format_arg_store make_format_args( return {args...}; } -template class dynamic_format_arg_store; +/** + \rst + A dynamic version of `fmt::format_arg_store<>`. + It's equipped with a storage to potentially temporary objects which lifetime + could be shorter than the format arguments object. + + It can be implicitly converted into `~fmt::basic_format_args` for passing + into type-erased formatting functions such as `~fmt::vformat`. + \endrst + */ +template +class dynamic_format_arg_store +#if FMT_GCC_VERSION < 409 + // Workaround a GCC template argument substitution bug. + : public basic_format_args +#endif +{ + private: + using char_type = typename Context::char_type; + + template struct need_dyn_copy { + static constexpr internal::type mapped_type = + internal::mapped_type_constant::value; + + using type = std::integral_constant< + bool, !(internal::is_reference_wrapper::value || + internal::is_string_view::value || + (mapped_type != internal::type::cstring_type && + mapped_type != internal::type::string_type && + mapped_type != internal::type::custom_type && + mapped_type != internal::type::named_arg_type))>; + }; + + template + using stored_type = conditional_t::value, + std::basic_string, T>; + + // Storage of basic_format_arg must be contiguous. + std::vector> data_; + + // Storage of arguments not fitting into basic_format_arg must grow + // without relocation because items in data_ refer to it. + internal::dyn_arg_storage storage_; + + friend class basic_format_args; + + unsigned long long get_types() const { + return internal::is_unpacked_bit | data_.size(); + } + + template void emplace_arg(const T& arg) { + data_.emplace_back(internal::make_arg(arg)); + } + + public: + dynamic_format_arg_store() = default; + ~dynamic_format_arg_store() = default; + + dynamic_format_arg_store(const dynamic_format_arg_store&) = delete; + dynamic_format_arg_store& operator=(const dynamic_format_arg_store&) = delete; + + dynamic_format_arg_store(dynamic_format_arg_store&&) = default; + dynamic_format_arg_store& operator=(dynamic_format_arg_store&&) = default; + + /** + \rst + Adds an argument into the dynamic store for later passing to a formating + function. + + Note that custom types and string types (but not string views!) are copied + into the store with dynamic memory (in addition to resizing vector). + + **Example**:: + + fmt::dynamic_format_arg_store store; + store.push_back(42); + store.push_back("abc"); + store.push_back(1.5f); + std::string result = fmt::vformat("{} and {} and {}", store); + \endrst + */ + template void push_back(const T& arg) { + static_assert( + !std::is_base_of, T>::value, + "named arguments are not supported yet"); + if (internal::const_check(need_dyn_copy::type::value)) + emplace_arg(storage_.push>(arg)); + else + emplace_arg(arg); + } + + /** + Adds a reference to the argument into the dynamic store for later passing to + a formating function. + */ + template void push_back(std::reference_wrapper arg) { + static_assert( + need_dyn_copy::type::value, + "objects of built-in types and string views are always copied"); + emplace_arg(arg.get()); + } +}; /** \rst @@ -1639,180 +1796,6 @@ inline void print(const S& format_str, Args&&... args) { internal::make_args_checked(format_str, args...)); #endif } - -namespace internal { - -template struct is_string_view : std::false_type {}; - -template -struct is_string_view, Char> : std::true_type {}; - -template -struct is_string_view, Char> : std::true_type {}; - -template struct is_reference_wrapper : std::false_type {}; - -template -struct is_reference_wrapper> : std::true_type {}; - -class dyn_arg_storage { - // Workaround for clang's -Wweak-vtables. Unlike for regular classes, for - // templates it doesn't complain about inability to deduce single translation - // unit for placing vtable. So storage_node_base is made a fake template. - - template struct storage_node_base { - using owning_ptr = std::unique_ptr>; - virtual ~storage_node_base() = default; - owning_ptr next; - }; - - template struct storage_node : storage_node_base<> { - T value; - template - FMT_CONSTEXPR storage_node(const Arg& arg, owning_ptr&& next) : value{arg} { - // Must be initialised after value_ - this->next = std::move(next); - } - - template - FMT_CONSTEXPR storage_node(const basic_string_view& arg, - owning_ptr&& next) - : value{arg.data(), arg.size()} { - // Must be initialised after value - this->next = std::move(next); - } - }; - - storage_node_base<>::owning_ptr head_{nullptr}; - - public: - dyn_arg_storage() = default; - dyn_arg_storage(const dyn_arg_storage&) = delete; - dyn_arg_storage(dyn_arg_storage&&) = default; - - dyn_arg_storage& operator=(const dyn_arg_storage&) = delete; - dyn_arg_storage& operator=(dyn_arg_storage&&) = default; - - template const T& push(const Arg& arg) { - auto node = new storage_node{arg, std::move(head_)}; - head_.reset(node); - return node->value; - } -}; - -} // namespace internal - -/** - \rst - A dynamic version of `fmt::format_arg_store<>`. - It's equipped with a storage to potentially temporary objects which lifetime - could be shorter than the format arguments object. - - It can be implicitly converted into `~fmt::basic_format_args` for passing - into type-erased formatting functions such as `~fmt::vformat`. - \endrst - */ -template -class dynamic_format_arg_store -#if FMT_GCC_VERSION < 409 - // Workaround a GCC template argument substitution bug. - : public basic_format_args -#endif -{ - private: - using char_type = typename Context::char_type; - - template struct need_dyn_copy { - static constexpr internal::type mapped_type = - internal::mapped_type_constant::value; -// static_assert( -// mapped_type != internal::type::named_arg_type, -// "Bug indicator. Named arguments must be processed separately"); - - using type = std::integral_constant< - bool, !(internal::is_reference_wrapper::value || - internal::is_string_view::value || - (mapped_type != internal::type::cstring_type && - mapped_type != internal::type::string_type && - mapped_type != internal::type::custom_type && - mapped_type != internal::type::named_arg_type))>; - }; - - template - using stored_type = conditional_t::value, - std::basic_string, T>; - - // Storage of basic_format_arg must be contiguous - // Required by basic_format_args::args_ which is just a pointer. - std::vector> data_; - - // Storage of arguments not fitting into basic_format_arg must grow - // without relocation because items in data_ refer to it. - - internal::dyn_arg_storage storage_; - - friend class basic_format_args; - - unsigned long long get_types() const { - return internal::is_unpacked_bit | data_.size(); - } - - template void emplace_arg(const T& arg) { - data_.emplace_back(internal::make_arg(arg)); - } - - public: - dynamic_format_arg_store() = default; - ~dynamic_format_arg_store() = default; - - dynamic_format_arg_store(const dynamic_format_arg_store&) = delete; - dynamic_format_arg_store& operator=(const dynamic_format_arg_store&) = delete; - - dynamic_format_arg_store(dynamic_format_arg_store&&) = default; - dynamic_format_arg_store& operator=(dynamic_format_arg_store&&) = default; - - /** - \rst - Adds an argument into the dynamic store for later passing to a formating - function. - - Note that custom types and string types (but not string views!) are copied - into the store with dynamic memory (in addition to resizing vector). - - **Example**:: - - #include - fmt::dynamic_format_arg_store store; - store.push_back(42); - store.push_back("abc1"); - store.push_back(1.5f); - std::string result = fmt::vformat("{} and {} and {}", store); - \endrst - */ - template void push_back(const T& arg) { - static_assert( - !std::is_base_of, T>::value, - "Named arguments are not supported yet"); - if (internal::const_check(need_dyn_copy::type::value)) - emplace_arg(storage_.push>(arg)); - else - emplace_arg(arg); - } - - /** - \rst - Adds an argument into the dynamic store for later passing to a formating - function without copying into type-erasing list. - \endrst - */ - template void push_back(std::reference_wrapper arg) { - static_assert( - need_dyn_copy::type::value, - "Primitive types and string views directly supported by " - "basic_format_arg. Passing them by reference is not allowed"); - emplace_arg(arg.get()); - } -}; FMT_END_NAMESPACE #endif // FMT_CORE_H_ diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 23ea9fcf..89176633 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -95,7 +95,6 @@ add_fmt_test(grisu-test) target_compile_definitions(grisu-test PRIVATE FMT_USE_GRISU=1) add_fmt_test(gtest-extra-test) add_fmt_test(format-test mock-allocator.h) -add_fmt_test(format-dyn-args-test) if (MSVC) target_compile_options(format-test PRIVATE /bigobj) endif () diff --git a/test/core-test.cc b/test/core-test.cc index 6a7c0d3b..c2e4593a 100644 --- a/test/core-test.cc +++ b/test/core-test.cc @@ -15,9 +15,8 @@ #include #include -#include "test-assert.h" - #include "gmock.h" +#include "test-assert.h" // Check if fmt/core.h compiles with windows.h included before it. #ifdef _WIN32 @@ -402,6 +401,83 @@ TEST(ArgTest, VisitInvalidArg) { fmt::visit_format_arg(visitor, arg); } +TEST(FormatDynArgsTest, Basic) { + fmt::dynamic_format_arg_store store; + store.push_back(42); + store.push_back("abc1"); + store.push_back(1.5f); + + std::string result = fmt::vformat("{} and {} and {}", store); + EXPECT_EQ("42 and abc1 and 1.5", result); +} + +TEST(FormatDynArgsTest, StringsAndRefs) { + // Unfortunately the tests are compiled with old ABI so strings use COW. + fmt::dynamic_format_arg_store store; + char str[] = "1234567890"; + store.push_back(str); + store.push_back(std::cref(str)); + store.push_back(fmt::string_view{str}); + str[0] = 'X'; + + std::string result = fmt::vformat("{} and {} and {}", store); + EXPECT_EQ("1234567890 and X234567890 and X234567890", result); +} + +struct custom_type { + int i = 0; +}; + +FMT_BEGIN_NAMESPACE +template <> struct formatter { + auto parse(format_parse_context& ctx) const -> decltype(ctx.begin()) { + return ctx.begin(); + } + + template + auto format(const custom_type& p, FormatContext& ctx) -> decltype(format_to( + ctx.out(), std::declval())) { + return format_to(ctx.out(), "cust={}", p.i); + } +}; +FMT_END_NAMESPACE + +TEST(FormatDynArgsTest, CustomFormat) { + fmt::dynamic_format_arg_store store; + custom_type c{}; + store.push_back(c); + ++c.i; + store.push_back(c); + ++c.i; + store.push_back(std::cref(c)); + ++c.i; + + std::string result = fmt::vformat("{} and {} and {}", store); + EXPECT_EQ("cust=0 and cust=1 and cust=3", result); +} + +TEST(FormatDynArgsTest, NamedArgByRef) { + fmt::dynamic_format_arg_store store; + + // Note: fmt::arg() constructs an object which holds a reference + // to its value. It's not an aggregate, so it doesn't extend the + // reference lifetime. As a result, it's a very bad idea passing temporary + // as a named argument value. Only GCC with optimization level >0 + // complains about this. + // + // A real life usecase is when you have both name and value alive + // guarantee their lifetime and thus don't want them to be copied into + // storages. + int a1_val{42}; + auto a1 = fmt::arg("a1_", a1_val); + store.push_back(std::cref(a1)); + + std::string result = fmt::vformat("{a1_}", // and {} and {}", + store); + + EXPECT_EQ("42", result); +} + TEST(StringViewTest, ValueType) { static_assert(std::is_same::value, ""); } diff --git a/test/format-dyn-args-test.cc b/test/format-dyn-args-test.cc index da4da7ff..acc5ef78 100644 --- a/test/format-dyn-args-test.cc +++ b/test/format-dyn-args-test.cc @@ -4,84 +4,3 @@ #include #include "gtest-extra.h" - -TEST(FormatDynArgsTest, Basic) { - fmt::dynamic_format_arg_store store; - store.push_back(42); - store.push_back("abc1"); - store.push_back(1.5f); - - std::string result = fmt::vformat("{} and {} and {}", store); - - EXPECT_EQ("42 and abc1 and 1.5", result); -} - -TEST(FormatDynArgsTest, StringsAndRefs) { - // Unfortunately the tests are compiled with old ABI - // So strings use COW. - fmt::dynamic_format_arg_store store; - char str[]{"1234567890"}; - store.push_back(str); - store.push_back(std::cref(str)); - store.push_back(fmt::string_view{str}); - str[0] = 'X'; - - std::string result = fmt::vformat("{} and {} and {}", store); - - EXPECT_EQ("1234567890 and X234567890 and X234567890", result); -} - -struct custom_type { - int i{0}; -}; -FMT_BEGIN_NAMESPACE - -template <> struct formatter { - auto parse(format_parse_context& ctx) const -> decltype(ctx.begin()) { - return ctx.begin(); - } - - template - auto format(const custom_type& p, FormatContext& ctx) -> decltype(format_to( - ctx.out(), std::declval())) { - return format_to(ctx.out(), "cust={}", p.i); - } -}; -FMT_END_NAMESPACE - -TEST(FormatDynArgsTest, CustomFormat) { - fmt::dynamic_format_arg_store store; - custom_type c{}; - store.push_back(c); - ++c.i; - store.push_back(c); - ++c.i; - store.push_back(std::cref(c)); - ++c.i; - - std::string result = fmt::vformat("{} and {} and {}", store); - - EXPECT_EQ("cust=0 and cust=1 and cust=3", result); -} - -TEST(FormatDynArgsTest, NamedArgByRef) { - fmt::dynamic_format_arg_store store; - - // Note: fmt::arg() constructs an object which holds a reference - // to its value. It's not an aggregate, so it doesn't extend the - // reference lifetime. As a result, it's a very bad idea passing temporary - // as a named argument value. Only GCC with optimization level >0 - // complains about this. - // - // A real life usecase is when you have both name and value alive - // guarantee their lifetime and thus don't want them to be copied into - // storages. - int a1_val{42}; - auto a1 = fmt::arg("a1_", a1_val); - store.push_back(std::cref(a1)); - - std::string result = fmt::vformat("{a1_}", // and {} and {}", - store); - - EXPECT_EQ("42", result); -} From 026f99178ef0698041c34493d344eafcfd7bbe3d Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Mon, 16 Mar 2020 19:10:41 -0700 Subject: [PATCH 43/45] Simplify dynamic store --- include/fmt/core.h | 35 +++++++++-------------------------- 1 file changed, 9 insertions(+), 26 deletions(-) diff --git a/include/fmt/core.h b/include/fmt/core.h index 8dc770b4..f58f512b 100644 --- a/include/fmt/core.h +++ b/include/fmt/core.h @@ -1206,14 +1206,6 @@ inline basic_format_arg make_arg(const T& value) { return make_arg(value); } -template struct is_string_view : std::false_type {}; - -template -struct is_string_view, Char> : std::true_type {}; - -template -struct is_string_view, Char> : std::true_type {}; - template struct is_reference_wrapper : std::false_type {}; template @@ -1233,31 +1225,21 @@ class dyn_arg_storage { T value; template - FMT_CONSTEXPR storage_node(const Arg& arg, owning_ptr&& next) : value(arg) { - this->next = std::move(next); // Must be initialised after value. - } + FMT_CONSTEXPR storage_node(const Arg& arg) : value(arg) {} template - FMT_CONSTEXPR storage_node(const basic_string_view& arg, - owning_ptr&& next) - : value(arg.data(), arg.size()) { - this->next = std::move(next); // Must be initialised after value. - } + FMT_CONSTEXPR storage_node(const basic_string_view& arg) + : value(arg.data(), arg.size()) {} }; - storage_node_base<>::owning_ptr head_{nullptr}; + storage_node_base<>::owning_ptr head_; public: - dyn_arg_storage() = default; - dyn_arg_storage(const dyn_arg_storage&) = delete; - dyn_arg_storage(dyn_arg_storage&&) = default; - - dyn_arg_storage& operator=(const dyn_arg_storage&) = delete; - dyn_arg_storage& operator=(dyn_arg_storage&&) = default; - template const T& push(const Arg& arg) { - auto node = new storage_node(arg, std::move(head_)); + auto next = std::move(head_); + auto node = new storage_node(arg); head_.reset(node); + head_->next = std::move(next); return node->value; } }; @@ -1396,7 +1378,8 @@ class dynamic_format_arg_store using type = std::integral_constant< bool, !(internal::is_reference_wrapper::value || - internal::is_string_view::value || + std::is_same>::value || + std::is_same>::value || (mapped_type != internal::type::cstring_type && mapped_type != internal::type::string_type && mapped_type != internal::type::custom_type && From 2559983e7a1f61617f92023b60e8865c179baf2f Mon Sep 17 00:00:00 2001 From: Spirrwell Date: Tue, 17 Mar 2020 09:24:42 -0400 Subject: [PATCH 44/45] Color formatting fixed for wide strings (fixes issue #1594) (#1596) * Use std::char_traits::length for ansi_color_escape::begin -Fixes issue #1594 https://github.com/fmtlib/fmt/issues/1594 --- include/fmt/color.h | 2 +- test/color-test.cc | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/include/fmt/color.h b/include/fmt/color.h index 3756ba3f..96d9ab6b 100644 --- a/include/fmt/color.h +++ b/include/fmt/color.h @@ -412,7 +412,7 @@ template struct ansi_color_escape { FMT_CONSTEXPR const Char* begin() const FMT_NOEXCEPT { return buffer; } FMT_CONSTEXPR const Char* end() const FMT_NOEXCEPT { - return buffer + std::strlen(buffer); + return buffer + std::char_traits::length(buffer); } private: diff --git a/test/color-test.cc b/test/color-test.cc index fde3a0c5..c1113a2e 100644 --- a/test/color-test.cc +++ b/test/color-test.cc @@ -50,6 +50,8 @@ TEST(ColorsTest, ColorsPrint) { TEST(ColorsTest, Format) { EXPECT_EQ(fmt::format(fg(fmt::rgb(255, 20, 30)), "rgb(255,20,30)"), "\x1b[38;2;255;020;030mrgb(255,20,30)\x1b[0m"); + EXPECT_EQ(fmt::format(fg(fmt::rgb(255, 20, 30)), L"rgb(255,20,30) wide"), + L"\x1b[38;2;255;020;030mrgb(255,20,30) wide\x1b[0m"); EXPECT_EQ(fmt::format(fg(fmt::color::blue), "blue"), "\x1b[38;2;000;000;255mblue\x1b[0m"); EXPECT_EQ( From 3cf619de553f7cad2e4704745839f02cd6d46bf7 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Tue, 17 Mar 2020 07:13:46 -0700 Subject: [PATCH 45/45] Simplify dynamic_format_arg_store --- include/fmt/core.h | 45 ++++++++++++++++++--------------------------- 1 file changed, 18 insertions(+), 27 deletions(-) diff --git a/include/fmt/core.h b/include/fmt/core.h index f58f512b..70647c76 100644 --- a/include/fmt/core.h +++ b/include/fmt/core.h @@ -1211,33 +1211,32 @@ template struct is_reference_wrapper : std::false_type {}; template struct is_reference_wrapper> : std::true_type {}; -class dyn_arg_storage { +class dynamic_arg_list { // Workaround for clang's -Wweak-vtables. Unlike for regular classes, for // templates it doesn't complain about inability to deduce single translation // unit for placing vtable. So storage_node_base is made a fake template. - template struct storage_node_base { - using owning_ptr = std::unique_ptr>; - virtual ~storage_node_base() = default; - owning_ptr next; + template struct node { + virtual ~node() = default; + std::unique_ptr> next; }; - template struct storage_node : storage_node_base<> { + template struct typed_node : node<> { T value; template - FMT_CONSTEXPR storage_node(const Arg& arg) : value(arg) {} + FMT_CONSTEXPR typed_node(const Arg& arg) : value(arg) {} template - FMT_CONSTEXPR storage_node(const basic_string_view& arg) + FMT_CONSTEXPR typed_node(const basic_string_view& arg) : value(arg.data(), arg.size()) {} }; - storage_node_base<>::owning_ptr head_; + std::unique_ptr> head_; public: template const T& push(const Arg& arg) { auto next = std::move(head_); - auto node = new storage_node(arg); + auto node = new typed_node(arg); head_.reset(node); head_->next = std::move(next); return node->value; @@ -1372,18 +1371,19 @@ class dynamic_format_arg_store private: using char_type = typename Context::char_type; - template struct need_dyn_copy { + template struct need_copy { static constexpr internal::type mapped_type = internal::mapped_type_constant::value; - using type = std::integral_constant< - bool, !(internal::is_reference_wrapper::value || + enum { + value = !(internal::is_reference_wrapper::value || std::is_same>::value || std::is_same>::value || (mapped_type != internal::type::cstring_type && mapped_type != internal::type::string_type && mapped_type != internal::type::custom_type && - mapped_type != internal::type::named_arg_type))>; + mapped_type != internal::type::named_arg_type)) + }; }; template @@ -1395,7 +1395,7 @@ class dynamic_format_arg_store // Storage of arguments not fitting into basic_format_arg must grow // without relocation because items in data_ refer to it. - internal::dyn_arg_storage storage_; + internal::dynamic_arg_list dynamic_args_; friend class basic_format_args; @@ -1408,15 +1408,6 @@ class dynamic_format_arg_store } public: - dynamic_format_arg_store() = default; - ~dynamic_format_arg_store() = default; - - dynamic_format_arg_store(const dynamic_format_arg_store&) = delete; - dynamic_format_arg_store& operator=(const dynamic_format_arg_store&) = delete; - - dynamic_format_arg_store(dynamic_format_arg_store&&) = default; - dynamic_format_arg_store& operator=(dynamic_format_arg_store&&) = default; - /** \rst Adds an argument into the dynamic store for later passing to a formating @@ -1438,8 +1429,8 @@ class dynamic_format_arg_store static_assert( !std::is_base_of, T>::value, "named arguments are not supported yet"); - if (internal::const_check(need_dyn_copy::type::value)) - emplace_arg(storage_.push>(arg)); + if (internal::const_check(need_copy::value)) + emplace_arg(dynamic_args_.push>(arg)); else emplace_arg(arg); } @@ -1450,7 +1441,7 @@ class dynamic_format_arg_store */ template void push_back(std::reference_wrapper arg) { static_assert( - need_dyn_copy::type::value, + need_copy::value, "objects of built-in types and string views are always copied"); emplace_arg(arg.get()); }