Compare commits

...

12 Commits

Author SHA1 Message Date
gabime
486b55554f Version 1.16.0 2025-10-11 15:53:05 +03:00
gabime
1bea38edcc clang-format 2025-10-11 15:50:16 +03:00
gabime
4418909af2 Bump fmt to 12.0.0 2025-10-11 15:18:15 +03:00
Angelio Mason
f1d748e5e3 Remove the fileapi.h include in os-inl.h (#3444)
* Replaced fileapi.h include with windows.h, as instructed in https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-flushfilebuffers
Otherwise this causes compilation error on older sdks.

* Removed the fileapi.h include entirely, since windows.h is already included before

---------

Co-authored-by: MasonAngelio <MasonAngelio>
2025-08-07 23:47:11 +03:00
Vitaly
3edc8036db Run tests in the order they are declared in the source file. (#3451)
Fixes an issue with running tests in random order in Catch2 3.9.0+.
2025-08-07 23:38:29 +03:00
Mihir Patel
9ecdf5c8a1 Added timeout for TCP calls such as connect, send, recv (#3432)
* Now lets test on windows

* I guess testing on windows passes.

* Update tcp_client-windows.h

Added default value to argument

* Final edit

* Update tcp_client-windows.h

Changed improper misplaced includes.
2025-07-17 23:47:35 +03:00
Gabi Melman
737347d2df Update linux.yml 2025-07-16 09:55:57 +03:00
Alexander
4f2b3d52f9 Update README.md (#3437)
* Update README.md

add example showcasing 2 loggers and `spdlog::set_level()` 
which set level not only to default logger, but to all registed loggers

* Update README.md

* simplify

* simplify
2025-07-16 08:59:51 +03:00
Joshua Chapman
4397dac510 chore(cmake): add option to override CMAKE_DEBUG_POSTFIX (#3433)
This will make it possible to use the pkg-config with CMake debug build.
2025-07-08 22:52:42 +03:00
Gabi Melman
6fd67ce169 Update windows.yml
remove msvc 2019 build
2025-07-06 10:08:55 +03:00
Gabi Melman
4619e18a16 Update windows.yml 2025-07-06 09:53:42 +03:00
Gabi Melman
a6215527f4 Fix ringbuffer tests for newline (#3436) 2025-07-06 08:38:48 +03:00
26 changed files with 1366 additions and 1118 deletions

View File

@@ -18,9 +18,8 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
config: config:
- { compiler: gcc, version: 7, build_type: Release, cppstd: 11 } - { compiler: gcc, version: 9, build_type: Release, cppstd: 11 }
- { compiler: gcc, version: 9, build_type: Release, cppstd: 17 } - { compiler: gcc, version: 11, build_type: Debug, cppstd: 17 }
- { compiler: gcc, version: 11, build_type: Debug, cppstd: 20 }
- { compiler: gcc, version: 12, build_type: Release, cppstd: 20 } - { compiler: gcc, version: 12, build_type: Release, cppstd: 20 }
- { compiler: gcc, version: 12, build_type: Debug, cppstd: 20, asan: ON } - { compiler: gcc, version: 12, build_type: Debug, cppstd: 20, asan: ON }
- { compiler: clang, version: 12, build_type: Debug, cppstd: 17 } - { compiler: clang, version: 12, build_type: Debug, cppstd: 17 }

View File

@@ -75,74 +75,5 @@ jobs:
run: | run: |
build\tests\${{ matrix.config.BUILD_TYPE }}\spdlog-utests.exe build\tests\${{ matrix.config.BUILD_TYPE }}\spdlog-utests.exe
# -----------------------------------------------------------------------
# MSVC 2019 build matrix
# -----------------------------------------------------------------------
build_2019:
runs-on: windows-2019
strategy:
fail-fast: true
matrix:
config:
- GENERATOR: "Visual Studio 16 2019"
BUILD_TYPE: Release
BUILD_SHARED: 'ON'
FATAL_ERRORS: 'ON'
WCHAR: 'OFF'
WCHAR_FILES: 'OFF'
BUILD_EXAMPLE: 'ON'
USE_STD_FORMAT: 'OFF'
CXX_STANDARD: 17
- GENERATOR: "Visual Studio 16 2019"
BUILD_TYPE: Release
BUILD_SHARED: 'ON'
FATAL_ERRORS: 'ON'
WCHAR: 'OFF'
WCHAR_FILES: 'OFF'
BUILD_EXAMPLE: 'ON'
USE_STD_FORMAT: 'OFF'
CXX_STANDARD: 14
- GENERATOR: "Visual Studio 16 2019"
BUILD_TYPE: Release
BUILD_SHARED: 'ON'
FATAL_ERRORS: 'ON'
WCHAR: 'OFF'
WCHAR_FILES: 'OFF'
BUILD_EXAMPLE: 'ON'
USE_STD_FORMAT: 'OFF'
CXX_STANDARD: 11
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: CMake ${{ matrix.config.GENERATOR }} CXX=${{matrix.config.CXX_STANDARD}} WCHAR=${{matrix.config.WCHAR_FILES}} STD_FORMAT=${{matrix.config.USE_STD_FORMAT}}
shell: pwsh
run: |
mkdir build
cd build
cmake -G "${{ matrix.config.GENERATOR }}" -A x64 `
-D CMAKE_BUILD_TYPE=${{ matrix.config.BUILD_TYPE }} `
-D BUILD_SHARED_LIBS=${{ matrix.config.BUILD_SHARED }} `
-D SPDLOG_WCHAR_SUPPORT=${{ matrix.config.WCHAR }} `
-D SPDLOG_WCHAR_FILENAMES=${{ matrix.config.WCHAR_FILES }} `
-D SPDLOG_BUILD_EXAMPLE=${{ matrix.config.BUILD_EXAMPLE }} `
-D SPDLOG_BUILD_EXAMPLE_HO=${{ matrix.config.BUILD_EXAMPLE }} `
-D SPDLOG_BUILD_TESTS=ON `
-D SPDLOG_BUILD_TESTS_HO=OFF `
-D SPDLOG_BUILD_WARNINGS=${{ matrix.config.FATAL_ERRORS }} `
-D SPDLOG_USE_STD_FORMAT=${{ matrix.config.USE_STD_FORMAT }} `
-D CMAKE_CXX_STANDARD=${{ matrix.config.CXX_STANDARD }} ..
- name: Build
shell: pwsh
run: |
cd build
cmake --build . --parallel --config ${{ matrix.config.BUILD_TYPE }}
- name: Run Tests
shell: pwsh
env:
PATH: ${{ env.PATH }};${{ github.workspace }}\build\_deps\catch2-build\src\${{ matrix.config.BUILD_TYPE }};${{ github.workspace }}\build\${{ matrix.config.BUILD_TYPE }}
run: |
build\tests\${{ matrix.config.BUILD_TYPE }}\spdlog-utests.exe

View File

@@ -62,6 +62,9 @@ option(SPDLOG_ENABLE_PCH "Build static or shared library using precompiled heade
# build position independent code # build position independent code
option(SPDLOG_BUILD_PIC "Build position independent code (-fPIC)" OFF) option(SPDLOG_BUILD_PIC "Build position independent code (-fPIC)" OFF)
# debug build postfix
set(SPDLOG_DEBUG_POSTFIX "d" CACHE STRING "Filename postfix for libraries in debug builds")
# example options # example options
option(SPDLOG_BUILD_EXAMPLE "Build example" ${SPDLOG_MASTER_PROJECT}) option(SPDLOG_BUILD_EXAMPLE "Build example" ${SPDLOG_MASTER_PROJECT})
option(SPDLOG_BUILD_EXAMPLE_HO "Build header only example" OFF) option(SPDLOG_BUILD_EXAMPLE_HO "Build header only example" OFF)
@@ -191,7 +194,7 @@ spdlog_enable_warnings(spdlog)
set_target_properties(spdlog PROPERTIES VERSION ${SPDLOG_VERSION} SOVERSION set_target_properties(spdlog PROPERTIES VERSION ${SPDLOG_VERSION} SOVERSION
${SPDLOG_VERSION_MAJOR}.${SPDLOG_VERSION_MINOR}) ${SPDLOG_VERSION_MAJOR}.${SPDLOG_VERSION_MINOR})
set_target_properties(spdlog PROPERTIES DEBUG_POSTFIX d) set_target_properties(spdlog PROPERTIES DEBUG_POSTFIX ${SPDLOG_DEBUG_POSTFIX})
if(COMMAND target_precompile_headers AND SPDLOG_ENABLE_PCH) if(COMMAND target_precompile_headers AND SPDLOG_ENABLE_PCH)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/cmake/pch.h.in ${PROJECT_BINARY_DIR}/spdlog_pch.h @ONLY) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/cmake/pch.h.in ${PROJECT_BINARY_DIR}/spdlog_pch.h @ONLY)

View File

@@ -80,7 +80,7 @@ int main()
spdlog::info("Positional args are {1} {0}..", "too", "supported"); spdlog::info("Positional args are {1} {0}..", "too", "supported");
spdlog::info("{:<30}", "left aligned"); spdlog::info("{:<30}", "left aligned");
spdlog::set_level(spdlog::level::debug); // Set global log level to debug spdlog::set_level(spdlog::level::debug); // Set *global* log level to debug
spdlog::debug("This message should be displayed.."); spdlog::debug("This message should be displayed..");
// change log pattern // change log pattern
@@ -224,7 +224,7 @@ void binary_example()
```c++ ```c++
// create a logger with 2 targets, with different log levels and formats. // create a logger with 2 targets, with different log levels and formats.
// The console will show only warnings or errors, while the file will log all. // The console will show only warnings or errors, while the file will log all.
void multi_sink_example() void multi_sink_example()
{ {
auto console_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>(); auto console_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
@@ -241,6 +241,29 @@ void multi_sink_example()
} }
``` ```
---
#### Register several loggers - change global level
```c++
// Creation of loggers. Set levels to all registered loggers.
void set_level_example()
{
auto logger1 = spdlog::basic_logger_mt("logger1", "logs/logger1.txt");
auto logger2 = spdlog::basic_logger_mt("logger2", "logs/logger2.txt");
spdlog::set_default_logger(logger2);
spdlog::default_logger()->set_level(spdlog::level::trace); // set level for the default logger (logger2) to trace
spdlog::trace("trace message to the logger2 (specified as default)");
spdlog::set_level(spdlog::level::off) // (sic!) set level for *all* registered loggers to off (disable)
logger1.warn("warn message will not appear because the level set to off");
logger2.warn("warn message will not appear because the level set to off");
spdlog::warn("warn message will not appear because the level set to off");
}
```
--- ---
#### User-defined callbacks about log events #### User-defined callbacks about log events
```c++ ```c++

View File

@@ -23,7 +23,6 @@
#ifdef _WIN32 #ifdef _WIN32
#include <spdlog/details/windows_include.h> #include <spdlog/details/windows_include.h>
#include <fileapi.h> // for FlushFileBuffers
#include <io.h> // for _get_osfhandle, _isatty, _fileno #include <io.h> // for _get_osfhandle, _isatty, _fileno
#include <process.h> // for _get_pid #include <process.h> // for _get_pid

View File

@@ -58,8 +58,81 @@ public:
SOCKET fd() const { return socket_; } SOCKET fd() const { return socket_; }
int connect_socket_with_timeout(SOCKET sockfd,
const struct sockaddr *addr,
int addrlen,
const timeval &tv) {
// If no timeout requested, do a normal blocking connect.
if (tv.tv_sec == 0 && tv.tv_usec == 0) {
int rv = ::connect(sockfd, addr, addrlen);
if (rv == SOCKET_ERROR && WSAGetLastError() == WSAEISCONN) {
return 0;
}
return rv;
}
// Switch to nonblocking mode
u_long mode = 1UL;
if (::ioctlsocket(sockfd, FIONBIO, &mode) == SOCKET_ERROR) {
return SOCKET_ERROR;
}
int rv = ::connect(sockfd, addr, addrlen);
int last_error = WSAGetLastError();
if (rv == 0 || last_error == WSAEISCONN) {
mode = 0UL;
if (::ioctlsocket(sockfd, FIONBIO, &mode) == SOCKET_ERROR) {
return SOCKET_ERROR;
}
return 0;
}
if (last_error != WSAEWOULDBLOCK) {
// Real error
mode = 0UL;
if (::ioctlsocket(sockfd, FIONBIO, &mode)) {
return SOCKET_ERROR;
}
return SOCKET_ERROR;
}
// Wait until socket is writable or timeout expires
fd_set wfds;
FD_ZERO(&wfds);
FD_SET(sockfd, &wfds);
rv = ::select(0, nullptr, &wfds, nullptr, const_cast<timeval *>(&tv));
// Restore blocking mode regardless of select result
mode = 0UL;
if (::ioctlsocket(sockfd, FIONBIO, &mode) == SOCKET_ERROR) {
return SOCKET_ERROR;
}
if (rv == 0) {
WSASetLastError(WSAETIMEDOUT);
return SOCKET_ERROR;
}
if (rv == SOCKET_ERROR) {
return SOCKET_ERROR;
}
int so_error = 0;
int len = sizeof(so_error);
if (::getsockopt(sockfd, SOL_SOCKET, SO_ERROR, reinterpret_cast<char *>(&so_error), &len) ==
SOCKET_ERROR) {
return SOCKET_ERROR;
}
if (so_error != 0 && so_error != WSAEISCONN) {
// connection failed
WSASetLastError(so_error);
return SOCKET_ERROR;
}
return 0; // success
}
// try to connect or throw on failure // try to connect or throw on failure
void connect(const std::string &host, int port) { void connect(const std::string &host, int port, int timeout_ms = 0) {
if (is_connected()) { if (is_connected()) {
close(); close();
} }
@@ -71,6 +144,10 @@ public:
hints.ai_flags = AI_NUMERICSERV; // port passed as as numeric value hints.ai_flags = AI_NUMERICSERV; // port passed as as numeric value
hints.ai_protocol = 0; hints.ai_protocol = 0;
timeval tv;
tv.tv_sec = timeout_ms / 1000;
tv.tv_usec = (timeout_ms % 1000) * 1000;
auto port_str = std::to_string(port); auto port_str = std::to_string(port);
struct addrinfo *addrinfo_result; struct addrinfo *addrinfo_result;
auto rv = ::getaddrinfo(host.c_str(), port_str.c_str(), &hints, &addrinfo_result); auto rv = ::getaddrinfo(host.c_str(), port_str.c_str(), &hints, &addrinfo_result);
@@ -82,7 +159,6 @@ public:
} }
// Try each address until we successfully connect(2). // Try each address until we successfully connect(2).
for (auto *rp = addrinfo_result; rp != nullptr; rp = rp->ai_next) { for (auto *rp = addrinfo_result; rp != nullptr; rp = rp->ai_next) {
socket_ = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); socket_ = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
if (socket_ == INVALID_SOCKET) { if (socket_ == INVALID_SOCKET) {
@@ -90,18 +166,24 @@ public:
WSACleanup(); WSACleanup();
continue; continue;
} }
if (::connect(socket_, rp->ai_addr, (int)rp->ai_addrlen) == 0) { if (connect_socket_with_timeout(socket_, rp->ai_addr, (int)rp->ai_addrlen, tv) == 0) {
last_error = 0;
break; break;
} else {
last_error = ::WSAGetLastError();
close();
} }
last_error = WSAGetLastError();
::closesocket(socket_);
socket_ = INVALID_SOCKET;
} }
::freeaddrinfo(addrinfo_result); ::freeaddrinfo(addrinfo_result);
if (socket_ == INVALID_SOCKET) { if (socket_ == INVALID_SOCKET) {
WSACleanup(); WSACleanup();
throw_winsock_error_("connect failed", last_error); throw_winsock_error_("connect failed", last_error);
} }
if (timeout_ms > 0) {
DWORD tv = static_cast<DWORD>(timeout_ms);
::setsockopt(socket_, SOL_SOCKET, SO_RCVTIMEO, (const char *)&tv, sizeof(tv));
::setsockopt(socket_, SOL_SOCKET, SO_SNDTIMEO, (const char *)&tv, sizeof(tv));
}
// set TCP_NODELAY // set TCP_NODELAY
int enable_flag = 1; int enable_flag = 1;

View File

@@ -39,8 +39,72 @@ public:
~tcp_client() { close(); } ~tcp_client() { close(); }
int connect_socket_with_timeout(int sockfd,
const struct sockaddr *addr,
socklen_t addrlen,
const timeval &tv) {
// Blocking connect if timeout is zero
if (tv.tv_sec == 0 && tv.tv_usec == 0) {
int rv = ::connect(sockfd, addr, addrlen);
if (rv < 0 && errno == EISCONN) {
// already connected, treat as success
return 0;
}
return rv;
}
// Non-blocking path
int orig_flags = ::fcntl(sockfd, F_GETFL, 0);
if (orig_flags < 0) {
return -1;
}
if (::fcntl(sockfd, F_SETFL, orig_flags | O_NONBLOCK) < 0) {
return -1;
}
int rv = ::connect(sockfd, addr, addrlen);
if (rv == 0 || (rv < 0 && errno == EISCONN)) {
// immediate connect or already connected
::fcntl(sockfd, F_SETFL, orig_flags);
return 0;
}
if (errno != EINPROGRESS) {
::fcntl(sockfd, F_SETFL, orig_flags);
return -1;
}
// wait for writability
fd_set wfds;
FD_ZERO(&wfds);
FD_SET(sockfd, &wfds);
struct timeval tv_copy = tv;
rv = ::select(sockfd + 1, nullptr, &wfds, nullptr, &tv_copy);
if (rv <= 0) {
// timeout or error
::fcntl(sockfd, F_SETFL, orig_flags);
if (rv == 0) errno = ETIMEDOUT;
return -1;
}
// check socket error
int so_error = 0;
socklen_t len = sizeof(so_error);
if (::getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &so_error, &len) < 0) {
::fcntl(sockfd, F_SETFL, orig_flags);
return -1;
}
::fcntl(sockfd, F_SETFL, orig_flags);
if (so_error != 0 && so_error != EISCONN) {
errno = so_error;
return -1;
}
return 0;
}
// try to connect or throw on failure // try to connect or throw on failure
void connect(const std::string &host, int port) { void connect(const std::string &host, int port, int timeout_ms = 0) {
close(); close();
struct addrinfo hints {}; struct addrinfo hints {};
memset(&hints, 0, sizeof(struct addrinfo)); memset(&hints, 0, sizeof(struct addrinfo));
@@ -49,6 +113,10 @@ public:
hints.ai_flags = AI_NUMERICSERV; // port passed as as numeric value hints.ai_flags = AI_NUMERICSERV; // port passed as as numeric value
hints.ai_protocol = 0; hints.ai_protocol = 0;
struct timeval tv;
tv.tv_sec = timeout_ms / 1000;
tv.tv_usec = (timeout_ms % 1000) * 1000;
auto port_str = std::to_string(port); auto port_str = std::to_string(port);
struct addrinfo *addrinfo_result; struct addrinfo *addrinfo_result;
auto rv = ::getaddrinfo(host.c_str(), port_str.c_str(), &hints, &addrinfo_result); auto rv = ::getaddrinfo(host.c_str(), port_str.c_str(), &hints, &addrinfo_result);
@@ -69,8 +137,9 @@ public:
last_errno = errno; last_errno = errno;
continue; continue;
} }
rv = ::connect(socket_, rp->ai_addr, rp->ai_addrlen); ::fcntl(socket_, F_SETFD, FD_CLOEXEC);
if (rv == 0) { if (connect_socket_with_timeout(socket_, rp->ai_addr, rp->ai_addrlen, tv) == 0) {
last_errno = 0;
break; break;
} }
last_errno = errno; last_errno = errno;
@@ -82,6 +151,12 @@ public:
throw_spdlog_ex("::connect failed", last_errno); throw_spdlog_ex("::connect failed", last_errno);
} }
if (timeout_ms > 0) {
// Set timeouts for send and recv
::setsockopt(socket_, SOL_SOCKET, SO_RCVTIMEO, (const char *)&tv, sizeof(tv));
::setsockopt(socket_, SOL_SOCKET, SO_SNDTIMEO, (const char *)&tv, sizeof(tv));
}
// set TCP_NODELAY // set TCP_NODELAY
int enable_flag = 1; int enable_flag = 1;
::setsockopt(socket_, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast<char *>(&enable_flag), ::setsockopt(socket_, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast<char *>(&enable_flag),

View File

@@ -71,7 +71,7 @@ class dynamic_arg_list {
* It can be implicitly converted into `fmt::basic_format_args` for passing * It can be implicitly converted into `fmt::basic_format_args` for passing
* into type-erased formatting functions such as `fmt::vformat`. * into type-erased formatting functions such as `fmt::vformat`.
*/ */
template <typename Context> class dynamic_format_arg_store { FMT_EXPORT template <typename Context> class dynamic_format_arg_store {
private: private:
using char_type = typename Context::char_type; using char_type = typename Context::char_type;
@@ -212,7 +212,7 @@ template <typename Context> class dynamic_format_arg_store {
} }
/// Returns the number of elements in the store. /// Returns the number of elements in the store.
size_t size() const noexcept { return data_.size(); } auto size() const noexcept -> size_t { return data_.size(); }
}; };
FMT_END_NAMESPACE FMT_END_NAMESPACE

View File

@@ -21,7 +21,7 @@
#endif #endif
// The fmt library version in the form major * 10000 + minor * 100 + patch. // The fmt library version in the form major * 10000 + minor * 100 + patch.
#define FMT_VERSION 110200 #define FMT_VERSION 120000
// Detect compiler versions. // Detect compiler versions.
#if defined(__clang__) && !defined(__ibmxl__) #if defined(__clang__) && !defined(__ibmxl__)
@@ -201,14 +201,6 @@
# define FMT_NODISCARD # define FMT_NODISCARD
#endif #endif
#ifdef FMT_DEPRECATED
// Use the provided definition.
#elif FMT_HAS_CPP14_ATTRIBUTE(deprecated)
# define FMT_DEPRECATED [[deprecated]]
#else
# define FMT_DEPRECATED /* deprecated */
#endif
#if FMT_GCC_VERSION || FMT_CLANG_VERSION #if FMT_GCC_VERSION || FMT_CLANG_VERSION
# define FMT_VISIBILITY(value) __attribute__((visibility(value))) # define FMT_VISIBILITY(value) __attribute__((visibility(value)))
#else #else
@@ -260,7 +252,7 @@ FMT_PRAGMA_CLANG(diagnostic push)
#ifndef FMT_BEGIN_NAMESPACE #ifndef FMT_BEGIN_NAMESPACE
# define FMT_BEGIN_NAMESPACE \ # define FMT_BEGIN_NAMESPACE \
namespace fmt { \ namespace fmt { \
inline namespace v11 { inline namespace v12 {
# define FMT_END_NAMESPACE \ # define FMT_END_NAMESPACE \
} \ } \
} }
@@ -356,6 +348,9 @@ template <typename T> constexpr auto max_of(T a, T b) -> T {
return a > b ? a : b; return a > b ? a : b;
} }
FMT_NORETURN FMT_API void assert_fail(const char* file, int line,
const char* message);
namespace detail { namespace detail {
// Suppresses "unused variable" warnings with the method described in // Suppresses "unused variable" warnings with the method described in
// https://herbsutter.com/2009/10/18/mailbag-shutting-up-compiler-warnings/. // https://herbsutter.com/2009/10/18/mailbag-shutting-up-compiler-warnings/.
@@ -396,7 +391,7 @@ FMT_NORETURN FMT_API void assert_fail(const char* file, int line,
# define FMT_ASSERT(condition, message) \ # define FMT_ASSERT(condition, message) \
((condition) /* void() fails with -Winvalid-constexpr on clang 4.0.1 */ \ ((condition) /* void() fails with -Winvalid-constexpr on clang 4.0.1 */ \
? (void)0 \ ? (void)0 \
: fmt::detail::assert_fail(__FILE__, __LINE__, (message))) : ::fmt::assert_fail(__FILE__, __LINE__, (message)))
#endif #endif
#ifdef FMT_USE_INT128 #ifdef FMT_USE_INT128
@@ -463,12 +458,13 @@ enum { use_utf8 = !FMT_WIN32 || is_utf8_enabled };
static_assert(!FMT_UNICODE || use_utf8, static_assert(!FMT_UNICODE || use_utf8,
"Unicode support requires compiling with /utf-8"); "Unicode support requires compiling with /utf-8");
template <typename T> constexpr const char* narrow(const T*) { return nullptr; } template <typename T> constexpr auto narrow(T*) -> char* { return nullptr; }
constexpr FMT_ALWAYS_INLINE const char* narrow(const char* s) { return s; } constexpr FMT_ALWAYS_INLINE auto narrow(const char* s) -> const char* {
return s;
}
template <typename Char> template <typename Char>
FMT_CONSTEXPR auto compare(const Char* s1, const Char* s2, std::size_t n) FMT_CONSTEXPR auto compare(const Char* s1, const Char* s2, size_t n) -> int {
-> int {
if (!is_constant_evaluated() && sizeof(Char) == 1) return memcmp(s1, s2, n); if (!is_constant_evaluated() && sizeof(Char) == 1) return memcmp(s1, s2, n);
for (; n != 0; ++s1, ++s2, --n) { for (; n != 0; ++s1, ++s2, --n) {
if (*s1 < *s2) return -1; if (*s1 < *s2) return -1;
@@ -540,7 +536,7 @@ template <typename Char> class basic_string_view {
FMT_CONSTEXPR20 basic_string_view(const Char* s) : data_(s) { FMT_CONSTEXPR20 basic_string_view(const Char* s) : data_(s) {
#if FMT_HAS_BUILTIN(__builtin_strlen) || FMT_GCC_VERSION || FMT_CLANG_VERSION #if FMT_HAS_BUILTIN(__builtin_strlen) || FMT_GCC_VERSION || FMT_CLANG_VERSION
if (std::is_same<Char, char>::value && !detail::is_constant_evaluated()) { if (std::is_same<Char, char>::value && !detail::is_constant_evaluated()) {
size_ = __builtin_strlen(detail::narrow(s)); // strlen is not costexpr. size_ = __builtin_strlen(detail::narrow(s)); // strlen is not constexpr.
return; return;
} }
#endif #endif
@@ -616,19 +612,6 @@ template <typename Char> class basic_string_view {
using string_view = basic_string_view<char>; using string_view = basic_string_view<char>;
// DEPRECATED! Will be merged with is_char and moved to detail.
template <typename T> struct is_xchar : std::false_type {};
template <> struct is_xchar<wchar_t> : std::true_type {};
template <> struct is_xchar<char16_t> : std::true_type {};
template <> struct is_xchar<char32_t> : std::true_type {};
#ifdef __cpp_char8_t
template <> struct is_xchar<char8_t> : std::true_type {};
#endif
// Specifies if `T` is a character (code unit) type.
template <typename T> struct is_char : is_xchar<T> {};
template <> struct is_char<char> : std::true_type {};
template <typename T> class basic_appender; template <typename T> class basic_appender;
using appender = basic_appender<char>; using appender = basic_appender<char>;
@@ -781,7 +764,7 @@ class basic_specs {
(static_cast<unsigned>(p) << precision_shift); (static_cast<unsigned>(p) << precision_shift);
} }
constexpr bool dynamic() const { constexpr auto dynamic() const -> bool {
return (data_ & (width_mask | precision_mask)) != 0; return (data_ & (width_mask | precision_mask)) != 0;
} }
@@ -921,14 +904,47 @@ template <typename Char = char> class parse_context {
FMT_CONSTEXPR void check_dynamic_spec(int arg_id); FMT_CONSTEXPR void check_dynamic_spec(int arg_id);
}; };
#ifndef FMT_USE_LOCALE
# define FMT_USE_LOCALE (FMT_OPTIMIZE_SIZE <= 1)
#endif
// A type-erased reference to std::locale to avoid the heavy <locale> include.
class locale_ref {
#if FMT_USE_LOCALE
private:
const void* locale_; // A type-erased pointer to std::locale.
public:
constexpr locale_ref() : locale_(nullptr) {}
template <typename Locale, FMT_ENABLE_IF(sizeof(Locale::collate) != 0)>
locale_ref(const Locale& loc);
inline explicit operator bool() const noexcept { return locale_ != nullptr; }
#endif // FMT_USE_LOCALE
public:
template <typename Locale> auto get() const -> Locale;
};
FMT_END_EXPORT FMT_END_EXPORT
namespace detail { namespace detail {
// Specifies if `T` is a code unit type.
template <typename T> struct is_code_unit : std::false_type {};
template <> struct is_code_unit<char> : std::true_type {};
template <> struct is_code_unit<wchar_t> : std::true_type {};
template <> struct is_code_unit<char16_t> : std::true_type {};
template <> struct is_code_unit<char32_t> : std::true_type {};
#ifdef __cpp_char8_t
template <> struct is_code_unit<char8_t> : bool_constant<is_utf8_enabled> {};
#endif
// Constructs fmt::basic_string_view<Char> from types implicitly convertible // Constructs fmt::basic_string_view<Char> from types implicitly convertible
// to it, deducing Char. Explicitly convertible types such as the ones returned // to it, deducing Char. Explicitly convertible types such as the ones returned
// from FMT_STRING are intentionally excluded. // from FMT_STRING are intentionally excluded.
template <typename Char, FMT_ENABLE_IF(is_char<Char>::value)> template <typename Char, FMT_ENABLE_IF(is_code_unit<Char>::value)>
constexpr auto to_string_view(const Char* s) -> basic_string_view<Char> { constexpr auto to_string_view(const Char* s) -> basic_string_view<Char> {
return s; return s;
} }
@@ -1057,11 +1073,11 @@ template <bool B1, bool B2, bool... Tail> constexpr auto count() -> int {
return (B1 ? 1 : 0) + count<B2, Tail...>(); return (B1 ? 1 : 0) + count<B2, Tail...>();
} }
template <typename... Args> constexpr auto count_named_args() -> int { template <typename... T> constexpr auto count_named_args() -> int {
return count<is_named_arg<Args>::value...>(); return count<is_named_arg<T>::value...>();
} }
template <typename... Args> constexpr auto count_static_named_args() -> int { template <typename... T> constexpr auto count_static_named_args() -> int {
return count<is_static_named_arg<Args>::value...>(); return count<is_static_named_arg<T>::value...>();
} }
template <typename Char> struct named_arg_info { template <typename Char> struct named_arg_info {
@@ -1069,7 +1085,7 @@ template <typename Char> struct named_arg_info {
int id; int id;
}; };
// named_args is non-const to suppress a bogus -Wmaybe-uninitalized in gcc 13. // named_args is non-const to suppress a bogus -Wmaybe-uninitialized in gcc 13.
template <typename Char> template <typename Char>
FMT_CONSTEXPR void check_for_duplicate(named_arg_info<Char>* named_args, FMT_CONSTEXPR void check_for_duplicate(named_arg_info<Char>* named_args,
int named_arg_index, int named_arg_index,
@@ -1173,7 +1189,7 @@ template <typename Char> struct type_mapper {
static auto map(ubitint<N>) static auto map(ubitint<N>)
-> conditional_t<N <= 64, unsigned long long, void>; -> conditional_t<N <= 64, unsigned long long, void>;
template <typename T, FMT_ENABLE_IF(is_char<T>::value)> template <typename T, FMT_ENABLE_IF(is_code_unit<T>::value)>
static auto map(T) -> conditional_t< static auto map(T) -> conditional_t<
std::is_same<T, char>::value || std::is_same<T, Char>::value, Char, void>; std::is_same<T, char>::value || std::is_same<T, Char>::value, Char, void>;
@@ -1679,12 +1695,12 @@ template <typename... T> struct arg_pack {};
template <typename Char, int NUM_ARGS, int NUM_NAMED_ARGS, bool DYNAMIC_NAMES> template <typename Char, int NUM_ARGS, int NUM_NAMED_ARGS, bool DYNAMIC_NAMES>
class format_string_checker { class format_string_checker {
private: private:
type types_[max_of(1, NUM_ARGS)]; type types_[max_of<size_t>(1, NUM_ARGS)];
named_arg_info<Char> named_args_[max_of(1, NUM_NAMED_ARGS)]; named_arg_info<Char> named_args_[max_of<size_t>(1, NUM_NAMED_ARGS)];
compile_parse_context<Char> context_; compile_parse_context<Char> context_;
using parse_func = auto (*)(parse_context<Char>&) -> const Char*; using parse_func = auto (*)(parse_context<Char>&) -> const Char*;
parse_func parse_funcs_[max_of(1, NUM_ARGS)]; parse_func parse_funcs_[max_of<size_t>(1, NUM_ARGS)];
public: public:
template <typename... T> template <typename... T>
@@ -2033,6 +2049,17 @@ struct has_back_insert_iterator_container_append<
.append(std::declval<InputIt>(), .append(std::declval<InputIt>(),
std::declval<InputIt>()))>> : std::true_type {}; std::declval<InputIt>()))>> : std::true_type {};
template <typename OutputIt, typename InputIt, typename = void>
struct has_back_insert_iterator_container_insert_at_end : std::false_type {};
template <typename OutputIt, typename InputIt>
struct has_back_insert_iterator_container_insert_at_end<
OutputIt, InputIt,
void_t<decltype(get_container(std::declval<OutputIt>())
.insert(get_container(std::declval<OutputIt>()).end(),
std::declval<InputIt>(),
std::declval<InputIt>()))>> : std::true_type {};
// An optimized version of std::copy with the output value type (T). // An optimized version of std::copy with the output value type (T).
template <typename T, typename InputIt, typename OutputIt, template <typename T, typename InputIt, typename OutputIt,
FMT_ENABLE_IF(is_back_insert_iterator<OutputIt>::value&& FMT_ENABLE_IF(is_back_insert_iterator<OutputIt>::value&&
@@ -2047,6 +2074,8 @@ FMT_CONSTEXPR20 auto copy(InputIt begin, InputIt end, OutputIt out)
template <typename T, typename InputIt, typename OutputIt, template <typename T, typename InputIt, typename OutputIt,
FMT_ENABLE_IF(is_back_insert_iterator<OutputIt>::value && FMT_ENABLE_IF(is_back_insert_iterator<OutputIt>::value &&
!has_back_insert_iterator_container_append< !has_back_insert_iterator_container_append<
OutputIt, InputIt>::value &&
has_back_insert_iterator_container_insert_at_end<
OutputIt, InputIt>::value)> OutputIt, InputIt>::value)>
FMT_CONSTEXPR20 auto copy(InputIt begin, InputIt end, OutputIt out) FMT_CONSTEXPR20 auto copy(InputIt begin, InputIt end, OutputIt out)
-> OutputIt { -> OutputIt {
@@ -2056,7 +2085,11 @@ FMT_CONSTEXPR20 auto copy(InputIt begin, InputIt end, OutputIt out)
} }
template <typename T, typename InputIt, typename OutputIt, template <typename T, typename InputIt, typename OutputIt,
FMT_ENABLE_IF(!is_back_insert_iterator<OutputIt>::value)> FMT_ENABLE_IF(!(is_back_insert_iterator<OutputIt>::value &&
(has_back_insert_iterator_container_append<
OutputIt, InputIt>::value ||
has_back_insert_iterator_container_insert_at_end<
OutputIt, InputIt>::value)))>
FMT_CONSTEXPR auto copy(InputIt begin, InputIt end, OutputIt out) -> OutputIt { FMT_CONSTEXPR auto copy(InputIt begin, InputIt end, OutputIt out) -> OutputIt {
while (begin != end) *out++ = static_cast<T>(*begin++); while (begin != end) *out++ = static_cast<T>(*begin++);
return out; return out;
@@ -2176,7 +2209,7 @@ template <typename Context> class value {
static_assert(N <= 64, "unsupported _BitInt"); static_assert(N <= 64, "unsupported _BitInt");
} }
template <typename T, FMT_ENABLE_IF(is_char<T>::value)> template <typename T, FMT_ENABLE_IF(is_code_unit<T>::value)>
constexpr FMT_INLINE value(T x FMT_BUILTIN) : char_value(x) { constexpr FMT_INLINE value(T x FMT_BUILTIN) : char_value(x) {
static_assert( static_assert(
std::is_same<T, char>::value || std::is_same<T, char_type>::value, std::is_same<T, char>::value || std::is_same<T, char_type>::value,
@@ -2252,7 +2285,7 @@ template <typename Context> class value {
custom.value = const_cast<value_type*>(&x); custom.value = const_cast<value_type*>(&x);
#endif #endif
} }
custom.format = format_custom<value_type, formatter<value_type, char_type>>; custom.format = format_custom<value_type>;
} }
template <typename T, FMT_ENABLE_IF(!has_formatter<T, char_type>())> template <typename T, FMT_ENABLE_IF(!has_formatter<T, char_type>())>
@@ -2263,10 +2296,10 @@ template <typename Context> class value {
} }
// Formats an argument of a custom type, such as a user-defined class. // Formats an argument of a custom type, such as a user-defined class.
template <typename T, typename Formatter> template <typename T>
static void format_custom(void* arg, parse_context<char_type>& parse_ctx, static void format_custom(void* arg, parse_context<char_type>& parse_ctx,
Context& ctx) { Context& ctx) {
auto f = Formatter(); auto f = formatter<T, char_type>();
parse_ctx.advance_to(f.parse(parse_ctx)); parse_ctx.advance_to(f.parse(parse_ctx));
using qualified_type = using qualified_type =
conditional_t<has_formatter<const T, char_type>(), const T, T>; conditional_t<has_formatter<const T, char_type>(), const T, T>;
@@ -2293,35 +2326,14 @@ struct is_output_iterator<
enable_if_t<std::is_assignable<decltype(*std::declval<decay_t<It>&>()++), enable_if_t<std::is_assignable<decltype(*std::declval<decay_t<It>&>()++),
T>::value>> : std::true_type {}; T>::value>> : std::true_type {};
#ifndef FMT_USE_LOCALE
# define FMT_USE_LOCALE (FMT_OPTIMIZE_SIZE <= 1)
#endif
// A type-erased reference to an std::locale to avoid a heavy <locale> include.
class locale_ref {
#if FMT_USE_LOCALE
private:
const void* locale_; // A type-erased pointer to std::locale.
public:
constexpr locale_ref() : locale_(nullptr) {}
template <typename Locale> locale_ref(const Locale& loc);
inline explicit operator bool() const noexcept { return locale_ != nullptr; }
#endif // FMT_USE_LOCALE
public:
template <typename Locale> auto get() const -> Locale;
};
template <typename> constexpr auto encode_types() -> unsigned long long { template <typename> constexpr auto encode_types() -> unsigned long long {
return 0; return 0;
} }
template <typename Context, typename Arg, typename... Args> template <typename Context, typename First, typename... T>
constexpr auto encode_types() -> unsigned long long { constexpr auto encode_types() -> unsigned long long {
return static_cast<unsigned>(stored_type_constant<Arg, Context>::value) | return static_cast<unsigned>(stored_type_constant<First, Context>::value) |
(encode_types<Context, Args...>() << packed_arg_bits); (encode_types<Context, T...>() << packed_arg_bits);
} }
template <typename Context, typename... T, size_t NUM_ARGS = sizeof...(T)> template <typename Context, typename... T, size_t NUM_ARGS = sizeof...(T)>
@@ -2338,8 +2350,9 @@ template <typename Context, int NUM_ARGS, int NUM_NAMED_ARGS,
unsigned long long DESC> unsigned long long DESC>
struct named_arg_store { struct named_arg_store {
// args_[0].named_args points to named_args to avoid bloating format_args. // args_[0].named_args points to named_args to avoid bloating format_args.
arg_t<Context, NUM_ARGS> args[1 + NUM_ARGS]; arg_t<Context, NUM_ARGS> args[1u + NUM_ARGS];
named_arg_info<typename Context::char_type> named_args[NUM_NAMED_ARGS]; named_arg_info<typename Context::char_type>
named_args[static_cast<size_t>(NUM_NAMED_ARGS)];
template <typename... T> template <typename... T>
FMT_CONSTEXPR FMT_ALWAYS_INLINE named_arg_store(T&... values) FMT_CONSTEXPR FMT_ALWAYS_INLINE named_arg_store(T&... values)
@@ -2358,8 +2371,8 @@ struct named_arg_store {
} }
named_arg_store(const named_arg_store& rhs) = delete; named_arg_store(const named_arg_store& rhs) = delete;
named_arg_store& operator=(const named_arg_store& rhs) = delete; auto operator=(const named_arg_store& rhs) -> named_arg_store& = delete;
named_arg_store& operator=(named_arg_store&& rhs) = delete; auto operator=(named_arg_store&& rhs) -> named_arg_store& = delete;
operator const arg_t<Context, NUM_ARGS>*() const { return args + 1; } operator const arg_t<Context, NUM_ARGS>*() const { return args + 1; }
}; };
@@ -2372,7 +2385,7 @@ struct format_arg_store {
// +1 to workaround a bug in gcc 7.5 that causes duplicated-branches warning. // +1 to workaround a bug in gcc 7.5 that causes duplicated-branches warning.
using type = using type =
conditional_t<NUM_NAMED_ARGS == 0, conditional_t<NUM_NAMED_ARGS == 0,
arg_t<Context, NUM_ARGS>[max_of(1, NUM_ARGS)], arg_t<Context, NUM_ARGS>[max_of<size_t>(1, NUM_ARGS)],
named_arg_store<Context, NUM_ARGS, NUM_NAMED_ARGS, DESC>>; named_arg_store<Context, NUM_ARGS, NUM_NAMED_ARGS, DESC>>;
type args; type args;
}; };
@@ -2656,22 +2669,17 @@ class context {
private: private:
appender out_; appender out_;
format_args args_; format_args args_;
FMT_NO_UNIQUE_ADDRESS detail::locale_ref loc_; FMT_NO_UNIQUE_ADDRESS locale_ref loc_;
public: public:
/// The character type for the output. using char_type = char; ///< The character type for the output.
using char_type = char;
using iterator = appender; using iterator = appender;
using format_arg = basic_format_arg<context>; using format_arg = basic_format_arg<context>;
using parse_context_type FMT_DEPRECATED = parse_context<>;
template <typename T> using formatter_type FMT_DEPRECATED = formatter<T>;
enum { builtin_types = FMT_BUILTIN_TYPES }; enum { builtin_types = FMT_BUILTIN_TYPES };
/// Constructs a `context` object. References to the arguments are stored /// Constructs a `context` object. References to the arguments are stored
/// in the object so make sure they have appropriate lifetimes. /// in the object so make sure they have appropriate lifetimes.
FMT_CONSTEXPR context(iterator out, format_args args, FMT_CONSTEXPR context(iterator out, format_args args, locale_ref loc = {})
detail::locale_ref loc = {})
: out_(out), args_(args), loc_(loc) {} : out_(out), args_(args), loc_(loc) {}
context(context&&) = default; context(context&&) = default;
context(const context&) = delete; context(const context&) = delete;
@@ -2692,7 +2700,7 @@ class context {
// Advances the begin iterator to `it`. // Advances the begin iterator to `it`.
FMT_CONSTEXPR void advance_to(iterator) {} FMT_CONSTEXPR void advance_to(iterator) {}
FMT_CONSTEXPR auto locale() const -> detail::locale_ref { return loc_; } FMT_CONSTEXPR auto locale() const -> locale_ref { return loc_; }
}; };
template <typename Char = char> struct runtime_format_string { template <typename Char = char> struct runtime_format_string {
@@ -2779,9 +2787,6 @@ template <typename T, typename Char = char>
concept formattable = is_formattable<remove_reference_t<T>, Char>::value; concept formattable = is_formattable<remove_reference_t<T>, Char>::value;
#endif #endif
template <typename T, typename Char>
using has_formatter FMT_DEPRECATED = std::is_constructible<formatter<T, Char>>;
// A formatter specialization for natively supported types. // A formatter specialization for natively supported types.
template <typename T, typename Char> template <typename T, typename Char>
struct formatter<T, Char, struct formatter<T, Char,
@@ -2978,9 +2983,9 @@ FMT_INLINE void println(format_string<T...> fmt, T&&... args) {
return fmt::println(stdout, fmt, static_cast<T&&>(args)...); return fmt::println(stdout, fmt, static_cast<T&&>(args)...);
} }
FMT_END_EXPORT
FMT_PRAGMA_CLANG(diagnostic pop) FMT_PRAGMA_CLANG(diagnostic pop)
FMT_PRAGMA_GCC(pop_options) FMT_PRAGMA_GCC(pop_options)
FMT_END_EXPORT
FMT_END_NAMESPACE FMT_END_NAMESPACE
#ifdef FMT_HEADER_ONLY #ifdef FMT_HEADER_ONLY

View File

@@ -38,6 +38,7 @@ FMT_BEGIN_NAMESPACE
// Copyright Paul Dreik 2019 // Copyright Paul Dreik 2019
namespace safe_duration_cast { namespace safe_duration_cast {
// DEPRECATED!
template <typename To, typename From, template <typename To, typename From,
FMT_ENABLE_IF(!std::is_same<From, To>::value && FMT_ENABLE_IF(!std::is_same<From, To>::value &&
std::numeric_limits<From>::is_signed == std::numeric_limits<From>::is_signed ==
@@ -161,17 +162,6 @@ auto safe_duration_cast(std::chrono::duration<FromRep, FromPeriod> from,
int& ec) -> To { int& ec) -> To {
using From = std::chrono::duration<FromRep, FromPeriod>; using From = std::chrono::duration<FromRep, FromPeriod>;
ec = 0; ec = 0;
if (std::isnan(from.count())) {
// nan in, gives nan out. easy.
return To{std::numeric_limits<typename To::rep>::quiet_NaN()};
}
// maybe we should also check if from is denormal, and decide what to do about
// it.
// +-inf should be preserved.
if (std::isinf(from.count())) {
return To{from.count()};
}
// the basic idea is that we need to convert from count() in the from type // the basic idea is that we need to convert from count() in the from type
// to count() in the To type, by multiplying it with this: // to count() in the To type, by multiplying it with this:
@@ -282,8 +272,6 @@ namespace detail {
#define FMT_NOMACRO #define FMT_NOMACRO
template <typename T = void> struct null {}; template <typename T = void> struct null {};
inline auto localtime_r FMT_NOMACRO(...) -> null<> { return null<>(); }
inline auto localtime_s(...) -> null<> { return null<>(); }
inline auto gmtime_r(...) -> null<> { return null<>(); } inline auto gmtime_r(...) -> null<> { return null<>(); }
inline auto gmtime_s(...) -> null<> { return null<>(); } inline auto gmtime_s(...) -> null<> { return null<>(); }
@@ -326,7 +314,7 @@ inline auto get_classic_locale() -> const std::locale& {
} }
template <typename CodeUnit> struct codecvt_result { template <typename CodeUnit> struct codecvt_result {
static constexpr const size_t max_size = 32; static constexpr size_t max_size = 32;
CodeUnit buf[max_size]; CodeUnit buf[max_size];
CodeUnit* end; CodeUnit* end;
}; };
@@ -443,11 +431,7 @@ auto duration_cast(std::chrono::duration<FromRep, FromPeriod> from) -> To {
using common_rep = typename std::common_type<FromRep, typename To::rep, using common_rep = typename std::common_type<FromRep, typename To::rep,
decltype(factor::num)>::type; decltype(factor::num)>::type;
common_rep count = from.count(); // This conversion is lossless.
int ec = 0;
auto count = safe_duration_cast::lossless_integral_conversion<common_rep>(
from.count(), ec);
if (ec) throw_duration_error();
// Multiply from.count() by factor and check for overflow. // Multiply from.count() by factor and check for overflow.
if (const_check(factor::num != 1)) { if (const_check(factor::num != 1)) {
@@ -458,6 +442,7 @@ auto duration_cast(std::chrono::duration<FromRep, FromPeriod> from) -> To {
count *= factor::num; count *= factor::num;
} }
if (const_check(factor::den != 1)) count /= factor::den; if (const_check(factor::den != 1)) count /= factor::den;
int ec = 0;
auto to = auto to =
To(safe_duration_cast::lossless_integral_conversion<typename To::rep>( To(safe_duration_cast::lossless_integral_conversion<typename To::rep>(
count, ec)); count, ec));
@@ -471,6 +456,8 @@ template <typename To, typename FromRep, typename FromPeriod,
std::is_floating_point<typename To::rep>::value)> std::is_floating_point<typename To::rep>::value)>
auto duration_cast(std::chrono::duration<FromRep, FromPeriod> from) -> To { auto duration_cast(std::chrono::duration<FromRep, FromPeriod> from) -> To {
#if FMT_SAFE_DURATION_CAST #if FMT_SAFE_DURATION_CAST
// Preserve infinity and NaN.
if (!isfinite(from.count())) return static_cast<To>(from.count());
// Throwing version of safe_duration_cast is only available for // Throwing version of safe_duration_cast is only available for
// integer to integer or float to float casts. // integer to integer or float to float casts.
int ec; int ec;
@@ -487,7 +474,7 @@ template <typename To, typename FromRep, typename FromPeriod,
FMT_ENABLE_IF( FMT_ENABLE_IF(
!is_similar_arithmetic_type<FromRep, typename To::rep>::value)> !is_similar_arithmetic_type<FromRep, typename To::rep>::value)>
auto duration_cast(std::chrono::duration<FromRep, FromPeriod> from) -> To { auto duration_cast(std::chrono::duration<FromRep, FromPeriod> from) -> To {
// Mixed integer <-> float cast is not supported by safe_duration_cast. // Mixed integer <-> float cast is not supported by safe duration_cast.
return std::chrono::duration_cast<To>(from); return std::chrono::duration_cast<To>(from);
} }
@@ -501,86 +488,10 @@ auto to_time_t(sys_time<Duration> time_point) -> std::time_t {
.count(); .count();
} }
namespace tz {
// DEPRECATED!
struct time_zone {
template <typename Duration, typename LocalTime>
auto to_sys(LocalTime) -> sys_time<Duration> {
return {};
}
};
template <typename... T> auto current_zone(T...) -> time_zone* {
return nullptr;
}
template <typename... T> void _tzset(T...) {}
} // namespace tz
// DEPRECATED!
inline void tzset_once() {
static bool init = []() {
using namespace tz;
_tzset();
return false;
}();
ignore_unused(init);
}
} // namespace detail } // namespace detail
FMT_BEGIN_EXPORT FMT_BEGIN_EXPORT
/**
* Converts given time since epoch as `std::time_t` value into calendar time,
* expressed in local time. Unlike `std::localtime`, this function is
* thread-safe on most platforms.
*/
FMT_DEPRECATED inline auto localtime(std::time_t time) -> std::tm {
struct dispatcher {
std::time_t time_;
std::tm tm_;
inline dispatcher(std::time_t t) : time_(t) {}
inline auto run() -> bool {
using namespace fmt::detail;
return handle(localtime_r(&time_, &tm_));
}
inline auto handle(std::tm* tm) -> bool { return tm != nullptr; }
inline auto handle(detail::null<>) -> bool {
using namespace fmt::detail;
return fallback(localtime_s(&tm_, &time_));
}
inline auto fallback(int res) -> bool { return res == 0; }
#if !FMT_MSC_VERSION
inline auto fallback(detail::null<>) -> bool {
using namespace fmt::detail;
std::tm* tm = std::localtime(&time_);
if (tm) tm_ = *tm;
return tm != nullptr;
}
#endif
};
dispatcher lt(time);
// Too big time values may be unsupported.
if (!lt.run()) FMT_THROW(format_error("time_t value out of range"));
return lt.tm_;
}
#if FMT_USE_LOCAL_TIME
template <typename Duration>
FMT_DEPRECATED auto localtime(std::chrono::local_time<Duration> time)
-> std::tm {
using namespace std::chrono;
using namespace detail::tz;
return localtime(detail::to_time_t(current_zone()->to_sys<Duration>(time)));
}
#endif
/** /**
* Converts given time since epoch as `std::time_t` value into calendar time, * Converts given time since epoch as `std::time_t` value into calendar time,
* expressed in Coordinated Universal Time (UTC). Unlike `std::gmtime`, this * expressed in Coordinated Universal Time (UTC). Unlike `std::gmtime`, this
@@ -652,7 +563,7 @@ inline void write_digit2_separated(char* buf, unsigned a, unsigned b,
// Add ASCII '0' to each digit byte and insert separators. // Add ASCII '0' to each digit byte and insert separators.
digits |= 0x3030003030003030 | (usep << 16) | (usep << 40); digits |= 0x3030003030003030 | (usep << 16) | (usep << 40);
constexpr const size_t len = 8; constexpr size_t len = 8;
if (const_check(is_big_endian())) { if (const_check(is_big_endian())) {
char tmp[len]; char tmp[len];
std::memcpy(tmp, &digits, len); std::memcpy(tmp, &digits, len);
@@ -1000,16 +911,16 @@ template <typename T>
struct has_tm_zone<T, void_t<decltype(T::tm_zone)>> : std::true_type {}; struct has_tm_zone<T, void_t<decltype(T::tm_zone)>> : std::true_type {};
template <typename T, FMT_ENABLE_IF(has_tm_zone<T>::value)> template <typename T, FMT_ENABLE_IF(has_tm_zone<T>::value)>
bool set_tm_zone(T& time, char* tz) { auto set_tm_zone(T& time, char* tz) -> bool {
time.tm_zone = tz; time.tm_zone = tz;
return true; return true;
} }
template <typename T, FMT_ENABLE_IF(!has_tm_zone<T>::value)> template <typename T, FMT_ENABLE_IF(!has_tm_zone<T>::value)>
bool set_tm_zone(T&, char*) { auto set_tm_zone(T&, char*) -> bool {
return false; return false;
} }
inline char* utc() { inline auto utc() -> char* {
static char tz[] = "UTC"; static char tz[] = "UTC";
return tz; return tz;
} }
@@ -2230,7 +2141,7 @@ template <typename Char> struct formatter<std::tm, Char> {
detail::handle_dynamic_spec(specs.dynamic_width(), specs.width, width_ref_, detail::handle_dynamic_spec(specs.dynamic_width(), specs.width, width_ref_,
ctx); ctx);
auto loc_ref = specs.localized() ? ctx.locale() : detail::locale_ref(); auto loc_ref = specs.localized() ? ctx.locale() : locale_ref();
detail::get_locale loc(static_cast<bool>(loc_ref), loc_ref); detail::get_locale loc(static_cast<bool>(loc_ref), loc_ref);
auto w = detail::tm_writer<basic_appender<Char>, Char, Duration>( auto w = detail::tm_writer<basic_appender<Char>, Char, Duration>(
loc, out, tm, subsecs); loc, out, tm, subsecs);

View File

@@ -375,19 +375,17 @@ template <typename Char> struct ansi_color_escape {
// 10 more. // 10 more.
if (is_background) value += 10u; if (is_background) value += 10u;
size_t index = 0; buffer[size++] = static_cast<Char>('\x1b');
buffer[index++] = static_cast<Char>('\x1b'); buffer[size++] = static_cast<Char>('[');
buffer[index++] = static_cast<Char>('[');
if (value >= 100u) { if (value >= 100u) {
buffer[index++] = static_cast<Char>('1'); buffer[size++] = static_cast<Char>('1');
value %= 100u; value %= 100u;
} }
buffer[index++] = static_cast<Char>('0' + value / 10u); buffer[size++] = static_cast<Char>('0' + value / 10u);
buffer[index++] = static_cast<Char>('0' + value % 10u); buffer[size++] = static_cast<Char>('0' + value % 10u);
buffer[index++] = static_cast<Char>('m'); buffer[size++] = static_cast<Char>('m');
buffer[index++] = static_cast<Char>('\0');
return; return;
} }
@@ -398,7 +396,7 @@ template <typename Char> struct ansi_color_escape {
to_esc(color.r, buffer + 7, ';'); to_esc(color.r, buffer + 7, ';');
to_esc(color.g, buffer + 11, ';'); to_esc(color.g, buffer + 11, ';');
to_esc(color.b, buffer + 15, 'm'); to_esc(color.b, buffer + 15, 'm');
buffer[19] = static_cast<Char>(0); size = 19;
} }
FMT_CONSTEXPR ansi_color_escape(emphasis em) noexcept { FMT_CONSTEXPR ansi_color_escape(emphasis em) noexcept {
uint8_t em_codes[num_emphases] = {}; uint8_t em_codes[num_emphases] = {};
@@ -411,26 +409,28 @@ template <typename Char> struct ansi_color_escape {
if (has_emphasis(em, emphasis::conceal)) em_codes[6] = 8; if (has_emphasis(em, emphasis::conceal)) em_codes[6] = 8;
if (has_emphasis(em, emphasis::strikethrough)) em_codes[7] = 9; if (has_emphasis(em, emphasis::strikethrough)) em_codes[7] = 9;
size_t index = 0; buffer[size++] = static_cast<Char>('\x1b');
buffer[size++] = static_cast<Char>('[');
for (size_t i = 0; i < num_emphases; ++i) { for (size_t i = 0; i < num_emphases; ++i) {
if (!em_codes[i]) continue; if (!em_codes[i]) continue;
buffer[index++] = static_cast<Char>('\x1b'); buffer[size++] = static_cast<Char>('0' + em_codes[i]);
buffer[index++] = static_cast<Char>('['); buffer[size++] = static_cast<Char>(';');
buffer[index++] = static_cast<Char>('0' + em_codes[i]);
buffer[index++] = static_cast<Char>('m');
} }
buffer[index++] = static_cast<Char>(0);
buffer[size - 1] = static_cast<Char>('m');
} }
FMT_CONSTEXPR operator const Char*() const noexcept { return buffer; } FMT_CONSTEXPR operator const Char*() const noexcept { return buffer; }
FMT_CONSTEXPR auto begin() const noexcept -> const Char* { return buffer; } FMT_CONSTEXPR auto begin() const noexcept -> const Char* { return buffer; }
FMT_CONSTEXPR20 auto end() const noexcept -> const Char* { FMT_CONSTEXPR auto end() const noexcept -> const Char* {
return buffer + basic_string_view<Char>(buffer).size(); return buffer + size;
} }
private: private:
static constexpr size_t num_emphases = 8; static constexpr size_t num_emphases = 8;
Char buffer[7u + 3u * num_emphases + 1u]; Char buffer[7u + 4u * num_emphases];
size_t size = 0;
static FMT_CONSTEXPR void to_esc(uint8_t c, Char* out, static FMT_CONSTEXPR void to_esc(uint8_t c, Char* out,
char delimiter) noexcept { char delimiter) noexcept {

View File

@@ -22,8 +22,6 @@ FMT_EXPORT class compiled_string {};
template <typename S> template <typename S>
struct is_compiled_string : std::is_base_of<compiled_string, S> {}; struct is_compiled_string : std::is_base_of<compiled_string, S> {};
namespace detail {
/** /**
* Converts a string literal `s` into a format string that will be parsed at * Converts a string literal `s` into a format string that will be parsed at
* compile time and converted into efficient formatting code. Requires C++17 * compile time and converted into efficient formatting code. Requires C++17
@@ -41,18 +39,40 @@ namespace detail {
# define FMT_COMPILE(s) FMT_STRING(s) # define FMT_COMPILE(s) FMT_STRING(s)
#endif #endif
/**
* Converts a string literal into a format string that will be parsed at
* compile time and converted into efficient formatting code. Requires support
* for class types in constant template parameters (a C++20 feature).
*
* **Example**:
*
* // Converts 42 into std::string using the most efficient method and no
* // runtime format string processing.
* using namespace fmt::literals;
* std::string s = fmt::format("{}"_cf, 42);
*/
#if FMT_USE_NONTYPE_TEMPLATE_ARGS
inline namespace literals {
template <detail::fixed_string Str> constexpr auto operator""_cf() {
return FMT_COMPILE(Str.data);
}
} // namespace literals
#endif
namespace detail {
template <typename T, typename... Tail> template <typename T, typename... Tail>
auto first(const T& value, const Tail&...) -> const T& { constexpr auto first(const T& value, const Tail&...) -> const T& {
return value; return value;
} }
#if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction) #if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction)
template <typename... Args> struct type_list {}; template <typename... T> struct type_list {};
// Returns a reference to the argument at index N from [first, rest...]. // Returns a reference to the argument at index N from [first, rest...].
template <int N, typename T, typename... Args> template <int N, typename T, typename... Args>
constexpr const auto& get([[maybe_unused]] const T& first, constexpr auto get([[maybe_unused]] const T& first,
[[maybe_unused]] const Args&... rest) { [[maybe_unused]] const Args&... rest) -> const auto& {
static_assert(N < 1 + sizeof...(Args), "index is out of bounds"); static_assert(N < 1 + sizeof...(Args), "index is out of bounds");
if constexpr (N == 0) if constexpr (N == 0)
return first; return first;
@@ -84,8 +104,8 @@ FMT_CONSTEXPR auto get_arg_index_by_name(basic_string_view<Char> name) -> int {
} }
template <typename Char, typename... Args> template <typename Char, typename... Args>
constexpr int get_arg_index_by_name(basic_string_view<Char> name, constexpr auto get_arg_index_by_name(basic_string_view<Char> name,
type_list<Args...>) { type_list<Args...>) -> int {
return get_arg_index_by_name<Args...>(name); return get_arg_index_by_name<Args...>(name);
} }
@@ -105,8 +125,8 @@ template <typename Char> struct text {
basic_string_view<Char> data; basic_string_view<Char> data;
using char_type = Char; using char_type = Char;
template <typename OutputIt, typename... Args> template <typename OutputIt, typename... T>
constexpr OutputIt format(OutputIt out, const Args&...) const { constexpr auto format(OutputIt out, const T&...) const -> OutputIt {
return write<Char>(out, data); return write<Char>(out, data);
} }
}; };
@@ -115,8 +135,8 @@ template <typename Char>
struct is_compiled_format<text<Char>> : std::true_type {}; struct is_compiled_format<text<Char>> : std::true_type {};
template <typename Char> template <typename Char>
constexpr text<Char> make_text(basic_string_view<Char> s, size_t pos, constexpr auto make_text(basic_string_view<Char> s, size_t pos, size_t size)
size_t size) { -> text<Char> {
return {{&s[pos], size}}; return {{&s[pos], size}};
} }
@@ -124,8 +144,8 @@ template <typename Char> struct code_unit {
Char value; Char value;
using char_type = Char; using char_type = Char;
template <typename OutputIt, typename... Args> template <typename OutputIt, typename... T>
constexpr OutputIt format(OutputIt out, const Args&...) const { constexpr auto format(OutputIt out, const T&...) const -> OutputIt {
*out++ = value; *out++ = value;
return out; return out;
} }
@@ -133,7 +153,7 @@ template <typename Char> struct code_unit {
// This ensures that the argument type is convertible to `const T&`. // This ensures that the argument type is convertible to `const T&`.
template <typename T, int N, typename... Args> template <typename T, int N, typename... Args>
constexpr const T& get_arg_checked(const Args&... args) { constexpr auto get_arg_checked(const Args&... args) -> const T& {
const auto& arg = detail::get<N>(args...); const auto& arg = detail::get<N>(args...);
if constexpr (detail::is_named_arg<remove_cvref_t<decltype(arg)>>()) { if constexpr (detail::is_named_arg<remove_cvref_t<decltype(arg)>>()) {
return arg.value; return arg.value;
@@ -146,13 +166,13 @@ template <typename Char>
struct is_compiled_format<code_unit<Char>> : std::true_type {}; struct is_compiled_format<code_unit<Char>> : std::true_type {};
// A replacement field that refers to argument N. // A replacement field that refers to argument N.
template <typename Char, typename T, int N> struct field { template <typename Char, typename V, int N> struct field {
using char_type = Char; using char_type = Char;
template <typename OutputIt, typename... Args> template <typename OutputIt, typename... T>
constexpr OutputIt format(OutputIt out, const Args&... args) const { constexpr auto format(OutputIt out, const T&... args) const -> OutputIt {
const T& arg = get_arg_checked<T, N>(args...); const V& arg = get_arg_checked<V, N>(args...);
if constexpr (std::is_convertible<T, basic_string_view<Char>>::value) { if constexpr (std::is_convertible<V, basic_string_view<Char>>::value) {
auto s = basic_string_view<Char>(arg); auto s = basic_string_view<Char>(arg);
return copy<Char>(s.begin(), s.end(), out); return copy<Char>(s.begin(), s.end(), out);
} else { } else {
@@ -170,10 +190,10 @@ template <typename Char> struct runtime_named_field {
basic_string_view<Char> name; basic_string_view<Char> name;
template <typename OutputIt, typename T> template <typename OutputIt, typename T>
constexpr static bool try_format_argument( constexpr static auto try_format_argument(
OutputIt& out, OutputIt& out,
// [[maybe_unused]] due to unused-but-set-parameter warning in GCC 7,8,9 // [[maybe_unused]] due to unused-but-set-parameter warning in GCC 7,8,9
[[maybe_unused]] basic_string_view<Char> arg_name, const T& arg) { [[maybe_unused]] basic_string_view<Char> arg_name, const T& arg) -> bool {
if constexpr (is_named_arg<typename std::remove_cv<T>::type>::value) { if constexpr (is_named_arg<typename std::remove_cv<T>::type>::value) {
if (arg_name == arg.name) { if (arg_name == arg.name) {
out = write<Char>(out, arg.value); out = write<Char>(out, arg.value);
@@ -183,8 +203,8 @@ template <typename Char> struct runtime_named_field {
return false; return false;
} }
template <typename OutputIt, typename... Args> template <typename OutputIt, typename... T>
constexpr OutputIt format(OutputIt out, const Args&... args) const { constexpr auto format(OutputIt out, const T&... args) const -> OutputIt {
bool found = (try_format_argument(out, name, args) || ...); bool found = (try_format_argument(out, name, args) || ...);
if (!found) { if (!found) {
FMT_THROW(format_error("argument with specified name is not found")); FMT_THROW(format_error("argument with specified name is not found"));
@@ -197,17 +217,17 @@ template <typename Char>
struct is_compiled_format<runtime_named_field<Char>> : std::true_type {}; struct is_compiled_format<runtime_named_field<Char>> : std::true_type {};
// A replacement field that refers to argument N and has format specifiers. // A replacement field that refers to argument N and has format specifiers.
template <typename Char, typename T, int N> struct spec_field { template <typename Char, typename V, int N> struct spec_field {
using char_type = Char; using char_type = Char;
formatter<T, Char> fmt; formatter<V, Char> fmt;
template <typename OutputIt, typename... Args> template <typename OutputIt, typename... T>
constexpr FMT_INLINE OutputIt format(OutputIt out, constexpr FMT_INLINE auto format(OutputIt out, const T&... args) const
const Args&... args) const { -> OutputIt {
const auto& vargs = const auto& vargs =
fmt::make_format_args<basic_format_context<OutputIt, Char>>(args...); fmt::make_format_args<basic_format_context<OutputIt, Char>>(args...);
basic_format_context<OutputIt, Char> ctx(out, vargs); basic_format_context<OutputIt, Char> ctx(out, vargs);
return fmt.format(get_arg_checked<T, N>(args...), ctx); return fmt.format(get_arg_checked<V, N>(args...), ctx);
} }
}; };
@@ -219,8 +239,8 @@ template <typename L, typename R> struct concat {
R rhs; R rhs;
using char_type = typename L::char_type; using char_type = typename L::char_type;
template <typename OutputIt, typename... Args> template <typename OutputIt, typename... T>
constexpr OutputIt format(OutputIt out, const Args&... args) const { constexpr auto format(OutputIt out, const T&... args) const -> OutputIt {
out = lhs.format(out, args...); out = lhs.format(out, args...);
return rhs.format(out, args...); return rhs.format(out, args...);
} }
@@ -230,14 +250,14 @@ template <typename L, typename R>
struct is_compiled_format<concat<L, R>> : std::true_type {}; struct is_compiled_format<concat<L, R>> : std::true_type {};
template <typename L, typename R> template <typename L, typename R>
constexpr concat<L, R> make_concat(L lhs, R rhs) { constexpr auto make_concat(L lhs, R rhs) -> concat<L, R> {
return {lhs, rhs}; return {lhs, rhs};
} }
struct unknown_format {}; struct unknown_format {};
template <typename Char> template <typename Char>
constexpr size_t parse_text(basic_string_view<Char> str, size_t pos) { constexpr auto parse_text(basic_string_view<Char> str, size_t pos) -> size_t {
for (size_t size = str.size(); pos != size; ++pos) { for (size_t size = str.size(); pos != size; ++pos) {
if (str[pos] == '{' || str[pos] == '}') break; if (str[pos] == '{' || str[pos] == '}') break;
} }
@@ -270,8 +290,8 @@ template <typename T, typename Char> struct parse_specs_result {
enum { manual_indexing_id = -1 }; enum { manual_indexing_id = -1 };
template <typename T, typename Char> template <typename T, typename Char>
constexpr parse_specs_result<T, Char> parse_specs(basic_string_view<Char> str, constexpr auto parse_specs(basic_string_view<Char> str, size_t pos,
size_t pos, int next_arg_id) { int next_arg_id) -> parse_specs_result<T, Char> {
str.remove_prefix(pos); str.remove_prefix(pos);
auto ctx = auto ctx =
compile_parse_context<Char>(str, max_value<int>(), nullptr, next_arg_id); compile_parse_context<Char>(str, max_value<int>(), nullptr, next_arg_id);
@@ -285,16 +305,16 @@ template <typename Char> struct arg_id_handler {
arg_id_kind kind; arg_id_kind kind;
arg_ref<Char> arg_id; arg_ref<Char> arg_id;
constexpr int on_auto() { constexpr auto on_auto() -> int {
FMT_ASSERT(false, "handler cannot be used with automatic indexing"); FMT_ASSERT(false, "handler cannot be used with automatic indexing");
return 0; return 0;
} }
constexpr int on_index(int id) { constexpr auto on_index(int id) -> int {
kind = arg_id_kind::index; kind = arg_id_kind::index;
arg_id = arg_ref<Char>(id); arg_id = arg_ref<Char>(id);
return 0; return 0;
} }
constexpr int on_name(basic_string_view<Char> id) { constexpr auto on_name(basic_string_view<Char> id) -> int {
kind = arg_id_kind::name; kind = arg_id_kind::name;
arg_id = arg_ref<Char>(id); arg_id = arg_ref<Char>(id);
return 0; return 0;
@@ -433,27 +453,28 @@ FMT_BEGIN_EXPORT
#if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction) #if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction)
template <typename CompiledFormat, typename... Args, template <typename CompiledFormat, typename... T,
typename Char = typename CompiledFormat::char_type, typename Char = typename CompiledFormat::char_type,
FMT_ENABLE_IF(detail::is_compiled_format<CompiledFormat>::value)> FMT_ENABLE_IF(detail::is_compiled_format<CompiledFormat>::value)>
FMT_INLINE std::basic_string<Char> format(const CompiledFormat& cf, FMT_INLINE FMT_CONSTEXPR_STRING auto format(const CompiledFormat& cf,
const Args&... args) { const T&... args)
-> std::basic_string<Char> {
auto s = std::basic_string<Char>(); auto s = std::basic_string<Char>();
cf.format(std::back_inserter(s), args...); cf.format(std::back_inserter(s), args...);
return s; return s;
} }
template <typename OutputIt, typename CompiledFormat, typename... Args, template <typename OutputIt, typename CompiledFormat, typename... T,
FMT_ENABLE_IF(detail::is_compiled_format<CompiledFormat>::value)> FMT_ENABLE_IF(detail::is_compiled_format<CompiledFormat>::value)>
constexpr FMT_INLINE OutputIt format_to(OutputIt out, const CompiledFormat& cf, constexpr FMT_INLINE auto format_to(OutputIt out, const CompiledFormat& cf,
const Args&... args) { const T&... args) -> OutputIt {
return cf.format(out, args...); return cf.format(out, args...);
} }
template <typename S, typename... Args, template <typename S, typename... T,
FMT_ENABLE_IF(is_compiled_string<S>::value)> FMT_ENABLE_IF(is_compiled_string<S>::value)>
FMT_INLINE std::basic_string<typename S::char_type> format(const S&, FMT_INLINE FMT_CONSTEXPR_STRING auto format(const S&, T&&... args)
Args&&... args) { -> std::basic_string<typename S::char_type> {
if constexpr (std::is_same<typename S::char_type, char>::value) { if constexpr (std::is_same<typename S::char_type, char>::value) {
constexpr auto str = basic_string_view<typename S::char_type>(S()); constexpr auto str = basic_string_view<typename S::char_type>(S());
if constexpr (str.size() == 2 && str[0] == '{' && str[1] == '}') { if constexpr (str.size() == 2 && str[0] == '{' && str[1] == '}') {
@@ -466,72 +487,97 @@ FMT_INLINE std::basic_string<typename S::char_type> format(const S&,
} }
} }
} }
constexpr auto compiled = detail::compile<Args...>(S()); constexpr auto compiled = detail::compile<T...>(S());
if constexpr (std::is_same<remove_cvref_t<decltype(compiled)>, if constexpr (std::is_same<remove_cvref_t<decltype(compiled)>,
detail::unknown_format>()) { detail::unknown_format>()) {
return fmt::format( return fmt::format(
static_cast<basic_string_view<typename S::char_type>>(S()), static_cast<basic_string_view<typename S::char_type>>(S()),
std::forward<Args>(args)...); std::forward<T>(args)...);
} else { } else {
return fmt::format(compiled, std::forward<Args>(args)...); return fmt::format(compiled, std::forward<T>(args)...);
} }
} }
template <typename OutputIt, typename S, typename... Args, template <typename OutputIt, typename S, typename... T,
FMT_ENABLE_IF(is_compiled_string<S>::value)> FMT_ENABLE_IF(is_compiled_string<S>::value)>
FMT_CONSTEXPR OutputIt format_to(OutputIt out, const S&, Args&&... args) { FMT_CONSTEXPR auto format_to(OutputIt out, const S&, T&&... args) -> OutputIt {
constexpr auto compiled = detail::compile<Args...>(S()); constexpr auto compiled = detail::compile<T...>(S());
if constexpr (std::is_same<remove_cvref_t<decltype(compiled)>, if constexpr (std::is_same<remove_cvref_t<decltype(compiled)>,
detail::unknown_format>()) { detail::unknown_format>()) {
return fmt::format_to( return fmt::format_to(
out, static_cast<basic_string_view<typename S::char_type>>(S()), out, static_cast<basic_string_view<typename S::char_type>>(S()),
std::forward<Args>(args)...); std::forward<T>(args)...);
} else { } else {
return fmt::format_to(out, compiled, std::forward<Args>(args)...); return fmt::format_to(out, compiled, std::forward<T>(args)...);
} }
} }
#endif #endif
template <typename OutputIt, typename S, typename... Args, template <typename OutputIt, typename S, typename... T,
FMT_ENABLE_IF(is_compiled_string<S>::value)> FMT_ENABLE_IF(is_compiled_string<S>::value)>
auto format_to_n(OutputIt out, size_t n, const S& fmt, Args&&... args) auto format_to_n(OutputIt out, size_t n, const S& fmt, T&&... args)
-> format_to_n_result<OutputIt> { -> format_to_n_result<OutputIt> {
using traits = detail::fixed_buffer_traits; using traits = detail::fixed_buffer_traits;
auto buf = detail::iterator_buffer<OutputIt, char, traits>(out, n); auto buf = detail::iterator_buffer<OutputIt, char, traits>(out, n);
fmt::format_to(std::back_inserter(buf), fmt, std::forward<Args>(args)...); fmt::format_to(std::back_inserter(buf), fmt, std::forward<T>(args)...);
return {buf.out(), buf.count()}; return {buf.out(), buf.count()};
} }
template <typename S, typename... Args, template <typename S, typename... T,
FMT_ENABLE_IF(is_compiled_string<S>::value)> FMT_ENABLE_IF(is_compiled_string<S>::value)>
FMT_CONSTEXPR20 auto formatted_size(const S& fmt, const Args&... args) FMT_CONSTEXPR20 auto formatted_size(const S& fmt, T&&... args) -> size_t {
-> size_t {
auto buf = detail::counting_buffer<>(); auto buf = detail::counting_buffer<>();
fmt::format_to(appender(buf), fmt, args...); fmt::format_to(appender(buf), fmt, std::forward<T>(args)...);
return buf.count(); return buf.count();
} }
template <typename S, typename... Args, template <typename S, typename... T,
FMT_ENABLE_IF(is_compiled_string<S>::value)> FMT_ENABLE_IF(is_compiled_string<S>::value)>
void print(std::FILE* f, const S& fmt, const Args&... args) { void print(std::FILE* f, const S& fmt, T&&... args) {
auto buf = memory_buffer(); auto buf = memory_buffer();
fmt::format_to(appender(buf), fmt, args...); fmt::format_to(appender(buf), fmt, std::forward<T>(args)...);
detail::print(f, {buf.data(), buf.size()}); detail::print(f, {buf.data(), buf.size()});
} }
template <typename S, typename... Args, template <typename S, typename... T,
FMT_ENABLE_IF(is_compiled_string<S>::value)> FMT_ENABLE_IF(is_compiled_string<S>::value)>
void print(const S& fmt, const Args&... args) { void print(const S& fmt, T&&... args) {
print(stdout, fmt, args...); print(stdout, fmt, std::forward<T>(args)...);
} }
#if FMT_USE_NONTYPE_TEMPLATE_ARGS template <size_t N> class static_format_result {
inline namespace literals { private:
template <detail::fixed_string Str> constexpr auto operator""_cf() { char data[N];
return FMT_COMPILE(Str.data);
} public:
} // namespace literals template <typename S, typename... T,
#endif FMT_ENABLE_IF(is_compiled_string<S>::value)>
explicit FMT_CONSTEXPR static_format_result(const S& fmt, T&&... args) {
*fmt::format_to(data, fmt, std::forward<T>(args)...) = '\0';
}
auto str() const -> fmt::string_view { return {data, N - 1}; }
auto c_str() const -> const char* { return data; }
};
/**
* Formats arguments according to the format string `fmt_str` and produces
* a string of the exact required size at compile time. Both the format string
* and the arguments must be compile-time expressions.
*
* The resulting string can be accessed as a C string via `c_str()` or as
* a `fmt::string_view` via `str()`.
*
* **Example**:
*
* // Produces the static string "42" at compile time.
* static constexpr auto result = FMT_STATIC_FORMAT("{}", 42);
* const char* s = result.c_str();
*/
#define FMT_STATIC_FORMAT(fmt_str, ...) \
fmt::static_format_result< \
fmt::formatted_size(FMT_COMPILE(fmt_str), __VA_ARGS__) + 1>( \
FMT_COMPILE(fmt_str), __VA_ARGS__)
FMT_END_EXPORT FMT_END_EXPORT
FMT_END_NAMESPACE FMT_END_NAMESPACE

View File

@@ -22,7 +22,7 @@
#include "format.h" #include "format.h"
#if FMT_USE_LOCALE #if FMT_USE_LOCALE && !defined(FMT_MODULE)
# include <locale> # include <locale>
#endif #endif
@@ -31,14 +31,49 @@
#endif #endif
FMT_BEGIN_NAMESPACE FMT_BEGIN_NAMESPACE
namespace detail {
#ifndef FMT_CUSTOM_ASSERT_FAIL
FMT_FUNC void assert_fail(const char* file, int line, const char* message) { FMT_FUNC void assert_fail(const char* file, int line, const char* message) {
// Use unchecked std::fprintf to avoid triggering another assertion when // Use unchecked std::fprintf to avoid triggering another assertion when
// writing to stderr fails. // writing to stderr fails.
fprintf(stderr, "%s:%d: assertion failed: %s", file, line, message); fprintf(stderr, "%s:%d: assertion failed: %s", file, line, message);
abort(); abort();
} }
#endif
#if FMT_USE_LOCALE
namespace detail {
using std::locale;
using std::numpunct;
using std::use_facet;
} // namespace detail
template <typename Locale, enable_if_t<(sizeof(Locale::collate) != 0), int>>
locale_ref::locale_ref(const Locale& loc) : locale_(&loc) {
static_assert(std::is_same<Locale, std::locale>::value, "");
}
#else
namespace detail {
struct locale {};
template <typename Char> struct numpunct {
auto grouping() const -> std::string { return "\03"; }
auto thousands_sep() const -> Char { return ','; }
auto decimal_point() const -> Char { return '.'; }
};
template <typename Facet> Facet use_facet(locale) { return {}; }
} // namespace detail
#endif // FMT_USE_LOCALE
template <typename Locale> auto locale_ref::get() const -> Locale {
using namespace detail;
static_assert(std::is_same<Locale, locale>::value, "");
#if FMT_USE_LOCALE
if (locale_) return *static_cast<const locale*>(locale_);
#endif
return locale();
}
namespace detail {
FMT_FUNC void format_error_code(detail::buffer<char>& out, int error_code, FMT_FUNC void format_error_code(detail::buffer<char>& out, int error_code,
string_view message) noexcept { string_view message) noexcept {
@@ -79,33 +114,6 @@ inline void fwrite_all(const void* ptr, size_t count, FILE* stream) {
FMT_THROW(system_error(errno, FMT_STRING("cannot write to file"))); FMT_THROW(system_error(errno, FMT_STRING("cannot write to file")));
} }
#if FMT_USE_LOCALE
using std::locale;
using std::numpunct;
using std::use_facet;
template <typename Locale>
locale_ref::locale_ref(const Locale& loc) : locale_(&loc) {
static_assert(std::is_same<Locale, locale>::value, "");
}
#else
struct locale {};
template <typename Char> struct numpunct {
auto grouping() const -> std::string { return "\03"; }
auto thousands_sep() const -> Char { return ','; }
auto decimal_point() const -> Char { return '.'; }
};
template <typename Facet> Facet use_facet(locale) { return {}; }
#endif // FMT_USE_LOCALE
template <typename Locale> auto locale_ref::get() const -> Locale {
static_assert(std::is_same<Locale, locale>::value, "");
#if FMT_USE_LOCALE
if (locale_) return *static_cast<const locale*>(locale_);
#endif
return locale();
}
template <typename Char> template <typename Char>
FMT_FUNC auto thousands_sep_impl(locale_ref loc) -> thousands_sep_result<Char> { FMT_FUNC auto thousands_sep_impl(locale_ref loc) -> thousands_sep_result<Char> {
auto&& facet = use_facet<numpunct<Char>>(loc.get<locale>()); auto&& facet = use_facet<numpunct<Char>>(loc.get<locale>());
@@ -133,14 +141,13 @@ FMT_FUNC auto write_loc(appender out, loc_value value,
} // namespace detail } // namespace detail
FMT_FUNC void report_error(const char* message) { FMT_FUNC void report_error(const char* message) {
#if FMT_USE_EXCEPTIONS #if FMT_MSC_VERSION || defined(__NVCC__)
// Use FMT_THROW instead of throw to avoid bogus unreachable code warnings // Silence unreachable code warnings in MSVC and NVCC because these
// from MSVC. // are nearly impossible to fix in a generic code.
FMT_THROW(format_error(message)); volatile bool b = true;
#else if (!b) return;
fputs(message, stderr);
abort();
#endif #endif
FMT_THROW(format_error(message));
} }
template <typename Locale> typename Locale::id format_facet<Locale>::id; template <typename Locale> typename Locale::id format_facet<Locale>::id;
@@ -174,11 +181,11 @@ inline auto operator==(basic_fp<F> x, basic_fp<F> y) -> bool {
} }
// Compilers should be able to optimize this into the ror instruction. // Compilers should be able to optimize this into the ror instruction.
FMT_CONSTEXPR inline auto rotr(uint32_t n, uint32_t r) noexcept -> uint32_t { FMT_INLINE auto rotr(uint32_t n, uint32_t r) noexcept -> uint32_t {
r &= 31; r &= 31;
return (n >> r) | (n << (32 - r)); return (n >> r) | (n << (32 - r));
} }
FMT_CONSTEXPR inline auto rotr(uint64_t n, uint32_t r) noexcept -> uint64_t { FMT_INLINE auto rotr(uint64_t n, uint32_t r) noexcept -> uint64_t {
r &= 63; r &= 63;
return (n >> r) | (n << (64 - r)); return (n >> r) | (n << (64 - r));
} }
@@ -275,7 +282,7 @@ template <> struct cache_accessor<float> {
static auto get_cached_power(int k) noexcept -> uint64_t { static auto get_cached_power(int k) noexcept -> uint64_t {
FMT_ASSERT(k >= float_info<float>::min_k && k <= float_info<float>::max_k, FMT_ASSERT(k >= float_info<float>::min_k && k <= float_info<float>::max_k,
"k is out of range"); "k is out of range");
static constexpr const uint64_t pow10_significands[] = { static constexpr uint64_t pow10_significands[] = {
0x81ceb32c4b43fcf5, 0xa2425ff75e14fc32, 0xcad2f7f5359a3b3f, 0x81ceb32c4b43fcf5, 0xa2425ff75e14fc32, 0xcad2f7f5359a3b3f,
0xfd87b5f28300ca0e, 0x9e74d1b791e07e49, 0xc612062576589ddb, 0xfd87b5f28300ca0e, 0x9e74d1b791e07e49, 0xc612062576589ddb,
0xf79687aed3eec552, 0x9abe14cd44753b53, 0xc16d9a0095928a28, 0xf79687aed3eec552, 0x9abe14cd44753b53, 0xc16d9a0095928a28,
@@ -370,7 +377,7 @@ template <> struct cache_accessor<double> {
FMT_ASSERT(k >= float_info<double>::min_k && k <= float_info<double>::max_k, FMT_ASSERT(k >= float_info<double>::min_k && k <= float_info<double>::max_k,
"k is out of range"); "k is out of range");
static constexpr const uint128_fallback pow10_significands[] = { static constexpr uint128_fallback pow10_significands[] = {
#if FMT_USE_FULL_CACHE_DRAGONBOX #if FMT_USE_FULL_CACHE_DRAGONBOX
{0xff77b1fcbebcdc4f, 0x25e8e89c13bb0f7b}, {0xff77b1fcbebcdc4f, 0x25e8e89c13bb0f7b},
{0x9faacf3df73609b1, 0x77b191618c54e9ad}, {0x9faacf3df73609b1, 0x77b191618c54e9ad},
@@ -1037,7 +1044,7 @@ template <> struct cache_accessor<double> {
#if FMT_USE_FULL_CACHE_DRAGONBOX #if FMT_USE_FULL_CACHE_DRAGONBOX
return pow10_significands[k - float_info<double>::min_k]; return pow10_significands[k - float_info<double>::min_k];
#else #else
static constexpr const uint64_t powers_of_5_64[] = { static constexpr uint64_t powers_of_5_64[] = {
0x0000000000000001, 0x0000000000000005, 0x0000000000000019, 0x0000000000000001, 0x0000000000000005, 0x0000000000000019,
0x000000000000007d, 0x0000000000000271, 0x0000000000000c35, 0x000000000000007d, 0x0000000000000271, 0x0000000000000c35,
0x0000000000003d09, 0x000000000001312d, 0x000000000005f5e1, 0x0000000000003d09, 0x000000000001312d, 0x000000000005f5e1,
@@ -1149,8 +1156,8 @@ auto is_left_endpoint_integer_shorter_interval(int exponent) noexcept -> bool {
exponent <= case_shorter_interval_left_endpoint_upper_threshold; exponent <= case_shorter_interval_left_endpoint_upper_threshold;
} }
// Remove trailing zeros from n and return the number of zeros removed (float) // Remove trailing zeros from n and return the number of zeros removed (float).
FMT_INLINE int remove_trailing_zeros(uint32_t& n, int s = 0) noexcept { FMT_INLINE auto remove_trailing_zeros(uint32_t& n, int s = 0) noexcept -> int {
FMT_ASSERT(n != 0, ""); FMT_ASSERT(n != 0, "");
// Modular inverse of 5 (mod 2^32): (mod_inv_5 * 5) mod 2^32 = 1. // Modular inverse of 5 (mod 2^32): (mod_inv_5 * 5) mod 2^32 = 1.
constexpr uint32_t mod_inv_5 = 0xcccccccd; constexpr uint32_t mod_inv_5 = 0xcccccccd;
@@ -1170,22 +1177,19 @@ FMT_INLINE int remove_trailing_zeros(uint32_t& n, int s = 0) noexcept {
return s; return s;
} }
// Removes trailing zeros and returns the number of zeros removed (double) // Removes trailing zeros and returns the number of zeros removed (double).
FMT_INLINE int remove_trailing_zeros(uint64_t& n) noexcept { FMT_INLINE auto remove_trailing_zeros(uint64_t& n) noexcept -> int {
FMT_ASSERT(n != 0, ""); FMT_ASSERT(n != 0, "");
// This magic number is ceil(2^90 / 10^8).
constexpr uint64_t magic_number = 12379400392853802749ull;
auto nm = umul128(n, magic_number);
// Is n is divisible by 10^8? // Is n is divisible by 10^8?
if ((nm.high() & ((1ull << (90 - 64)) - 1)) == 0 && nm.low() < magic_number) { constexpr uint32_t ten_pow_8 = 100000000u;
if ((n % ten_pow_8) == 0) {
// If yes, work with the quotient... // If yes, work with the quotient...
auto n32 = static_cast<uint32_t>(nm.high() >> (90 - 64)); auto n32 = static_cast<uint32_t>(n / ten_pow_8);
// ... and use the 32 bit variant of the function // ... and use the 32 bit variant of the function
int s = remove_trailing_zeros(n32, 8); int num_zeros = remove_trailing_zeros(n32, 8);
n = n32; n = n32;
return s; return num_zeros;
} }
// If n is not divisible by 10^8, work with n itself. // If n is not divisible by 10^8, work with n itself.
@@ -1210,7 +1214,7 @@ FMT_INLINE int remove_trailing_zeros(uint64_t& n) noexcept {
// The main algorithm for shorter interval case // The main algorithm for shorter interval case
template <typename T> template <typename T>
FMT_INLINE decimal_fp<T> shorter_interval_case(int exponent) noexcept { FMT_INLINE auto shorter_interval_case(int exponent) noexcept -> decimal_fp<T> {
decimal_fp<T> ret_value; decimal_fp<T> ret_value;
// Compute k and beta // Compute k and beta
const int minus_k = floor_log10_pow2_minus_log10_4_over_3(exponent); const int minus_k = floor_log10_pow2_minus_log10_4_over_3(exponent);
@@ -1454,8 +1458,8 @@ FMT_FUNC void vformat_to(buffer<char>& buf, string_view fmt, format_args args,
auto out = appender(buf); auto out = appender(buf);
if (fmt.size() == 2 && equal2(fmt.data(), "{}")) if (fmt.size() == 2 && equal2(fmt.data(), "{}"))
return args.get(0).visit(default_arg_formatter<char>{out}); return args.get(0).visit(default_arg_formatter<char>{out});
parse_format_string( parse_format_string(fmt,
fmt, format_handler<char>{parse_context<char>(fmt), {out, args, loc}}); format_handler<>{parse_context<>(fmt), {out, args, loc}});
} }
template <typename T> struct span { template <typename T> struct span {
@@ -1546,10 +1550,11 @@ template <typename F> class glibc_file : public file_base<F> {
void advance_write_buffer(size_t size) { this->file_->_IO_write_ptr += size; } void advance_write_buffer(size_t size) { this->file_->_IO_write_ptr += size; }
bool needs_flush() const { auto needs_flush() const -> bool {
if ((this->file_->_flags & line_buffered) == 0) return false; if ((this->file_->_flags & line_buffered) == 0) return false;
char* end = this->file_->_IO_write_end; char* end = this->file_->_IO_write_end;
return memchr(end, '\n', to_unsigned(this->file_->_IO_write_ptr - end)); auto size = max_of<ptrdiff_t>(this->file_->_IO_write_ptr - end, 0);
return memchr(end, '\n', static_cast<size_t>(size));
} }
void flush() { fflush_unlocked(this->file_); } void flush() { fflush_unlocked(this->file_); }
@@ -1573,7 +1578,7 @@ template <typename F> class apple_file : public file_base<F> {
void init_buffer() { void init_buffer() {
if (this->file_->_p) return; if (this->file_->_p) return;
// Force buffer initialization by placing and removing a char in a buffer. // Force buffer initialization by placing and removing a char in a buffer.
putc_unlocked(0, this->file_); if (!FMT_CLANG_ANALYZER) putc_unlocked(0, this->file_);
--this->file_->_p; --this->file_->_p;
++this->file_->_w; ++this->file_->_w;
} }
@@ -1594,7 +1599,7 @@ template <typename F> class apple_file : public file_base<F> {
this->file_->_w -= size; this->file_->_w -= size;
} }
bool needs_flush() const { auto needs_flush() const -> bool {
if ((this->file_->_flags & line_buffered) == 0) return false; if ((this->file_->_flags & line_buffered) == 0) return false;
return memchr(this->file_->_p + this->file_->_w, '\n', return memchr(this->file_->_p + this->file_->_w, '\n',
to_unsigned(-this->file_->_w)); to_unsigned(-this->file_->_w));

File diff suppressed because it is too large Load Diff

View File

@@ -29,7 +29,8 @@
# if (FMT_HAS_INCLUDE(<fcntl.h>) || defined(__APPLE__) || \ # if (FMT_HAS_INCLUDE(<fcntl.h>) || defined(__APPLE__) || \
defined(__linux__)) && \ defined(__linux__)) && \
(!defined(WINAPI_FAMILY) || \ (!defined(WINAPI_FAMILY) || \
(WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP)) (WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP)) && \
!defined(__wasm__)
# include <fcntl.h> // for O_RDONLY # include <fcntl.h> // for O_RDONLY
# define FMT_USE_FCNTL 1 # define FMT_USE_FCNTL 1
# else # else

View File

@@ -33,8 +33,8 @@
FMT_BEGIN_NAMESPACE FMT_BEGIN_NAMESPACE
namespace detail { namespace detail {
// Generate a unique explicit instantion in every translation unit using a tag // Generate a unique explicit instantiation in every translation unit using a
// type in an anonymous namespace. // tag type in an anonymous namespace.
namespace { namespace {
struct file_access_tag {}; struct file_access_tag {};
} // namespace } // namespace

View File

@@ -9,7 +9,7 @@
#define FMT_PRINTF_H_ #define FMT_PRINTF_H_
#ifndef FMT_MODULE #ifndef FMT_MODULE
# include <algorithm> // std::max # include <algorithm> // std::find
# include <limits> // std::numeric_limits # include <limits> // std::numeric_limits
#endif #endif
@@ -18,10 +18,6 @@
FMT_BEGIN_NAMESPACE FMT_BEGIN_NAMESPACE
FMT_BEGIN_EXPORT FMT_BEGIN_EXPORT
template <typename T> struct printf_formatter {
printf_formatter() = delete;
};
template <typename Char> class basic_printf_context { template <typename Char> class basic_printf_context {
private: private:
basic_appender<Char> out_; basic_appender<Char> out_;
@@ -33,8 +29,6 @@ template <typename Char> class basic_printf_context {
public: public:
using char_type = Char; using char_type = Char;
using parse_context_type = parse_context<Char>;
template <typename T> using formatter_type = printf_formatter<T>;
enum { builtin_types = 1 }; enum { builtin_types = 1 };
/// Constructs a `printf_context` object. References to the arguments are /// Constructs a `printf_context` object. References to the arguments are
@@ -46,7 +40,7 @@ template <typename Char> class basic_printf_context {
auto out() -> basic_appender<Char> { return out_; } auto out() -> basic_appender<Char> { return out_; }
void advance_to(basic_appender<Char>) {} void advance_to(basic_appender<Char>) {}
auto locale() -> detail::locale_ref { return {}; } auto locale() -> locale_ref { return {}; }
auto arg(int id) const -> basic_format_arg<basic_printf_context> { auto arg(int id) const -> basic_format_arg<basic_printf_context> {
return args_.get(id); return args_.get(id);
@@ -74,10 +68,9 @@ inline auto find<false, char>(const char* first, const char* last, char value,
// Checks if a value fits in int - used to avoid warnings about comparing // Checks if a value fits in int - used to avoid warnings about comparing
// signed and unsigned integers. // signed and unsigned integers.
template <bool IsSigned> struct int_checker { template <bool IS_SIGNED> struct int_checker {
template <typename T> static auto fits_in_int(T value) -> bool { template <typename T> static auto fits_in_int(T value) -> bool {
unsigned max = to_unsigned(max_value<int>()); return value <= to_unsigned(max_value<int>());
return value <= max;
} }
inline static auto fits_in_int(bool) -> bool { return true; } inline static auto fits_in_int(bool) -> bool { return true; }
}; };
@@ -95,7 +88,7 @@ struct printf_precision_handler {
auto operator()(T value) -> int { auto operator()(T value) -> int {
if (!int_checker<std::numeric_limits<T>::is_signed>::fits_in_int(value)) if (!int_checker<std::numeric_limits<T>::is_signed>::fits_in_int(value))
report_error("number is too big"); report_error("number is too big");
return (std::max)(static_cast<int>(value), 0); return max_of(static_cast<int>(value), 0);
} }
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)> template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
@@ -410,7 +403,9 @@ void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
arg_index = parse_ctx.next_arg_id(); arg_index = parse_ctx.next_arg_id();
else else
parse_ctx.check_arg_id(--arg_index); parse_ctx.check_arg_id(--arg_index);
return detail::get_arg(context, arg_index); auto arg = context.arg(arg_index);
if (!arg) report_error("argument not found");
return arg;
}; };
const Char* start = parse_ctx.begin(); const Char* start = parse_ctx.begin();
@@ -571,15 +566,19 @@ inline auto vsprintf(basic_string_view<Char> fmt,
* *
* std::string message = fmt::sprintf("The answer is %d", 42); * std::string message = fmt::sprintf("The answer is %d", 42);
*/ */
template <typename S, typename... T, typename Char = detail::char_t<S>> template <typename... T>
inline auto sprintf(const S& fmt, const T&... args) -> std::basic_string<Char> { inline auto sprintf(string_view fmt, const T&... args) -> std::string {
return vsprintf(detail::to_string_view(fmt), return vsprintf(fmt, make_printf_args(args...));
fmt::make_format_args<basic_printf_context<Char>>(args...)); }
template <typename... T>
FMT_DEPRECATED auto sprintf(basic_string_view<wchar_t> fmt, const T&... args)
-> std::wstring {
return vsprintf(fmt, make_printf_args<wchar_t>(args...));
} }
template <typename Char> template <typename Char>
inline auto vfprintf(std::FILE* f, basic_string_view<Char> fmt, auto vfprintf(std::FILE* f, basic_string_view<Char> fmt,
typename vprintf_args<Char>::type args) -> int { typename vprintf_args<Char>::type args) -> int {
auto buf = basic_memory_buffer<Char>(); auto buf = basic_memory_buffer<Char>();
detail::vprintf(buf, fmt, args); detail::vprintf(buf, fmt, args);
size_t size = buf.size(); size_t size = buf.size();
@@ -596,17 +595,14 @@ inline auto vfprintf(std::FILE* f, basic_string_view<Char> fmt,
* *
* fmt::fprintf(stderr, "Don't %s!", "panic"); * fmt::fprintf(stderr, "Don't %s!", "panic");
*/ */
template <typename S, typename... T, typename Char = detail::char_t<S>> template <typename... T>
inline auto fprintf(std::FILE* f, const S& fmt, const T&... args) -> int { inline auto fprintf(std::FILE* f, string_view fmt, const T&... args) -> int {
return vfprintf(f, detail::to_string_view(fmt), return vfprintf(f, fmt, make_printf_args(args...));
make_printf_args<Char>(args...));
} }
template <typename... T>
template <typename Char> FMT_DEPRECATED auto fprintf(std::FILE* f, basic_string_view<wchar_t> fmt,
FMT_DEPRECATED inline auto vprintf(basic_string_view<Char> fmt, const T&... args) -> int {
typename vprintf_args<Char>::type args) return vfprintf(f, fmt, make_printf_args<wchar_t>(args...));
-> int {
return vfprintf(stdout, fmt, args);
} }
/** /**
@@ -621,11 +617,6 @@ template <typename... T>
inline auto printf(string_view fmt, const T&... args) -> int { inline auto printf(string_view fmt, const T&... args) -> int {
return vfprintf(stdout, fmt, make_printf_args(args...)); return vfprintf(stdout, fmt, make_printf_args(args...));
} }
template <typename... T>
FMT_DEPRECATED inline auto printf(basic_string_view<wchar_t> fmt,
const T&... args) -> int {
return vfprintf(stdout, fmt, make_printf_args<wchar_t>(args...));
}
FMT_END_EXPORT FMT_END_EXPORT
FMT_END_NAMESPACE FMT_END_NAMESPACE

View File

@@ -11,7 +11,6 @@
#ifndef FMT_MODULE #ifndef FMT_MODULE
# include <initializer_list> # include <initializer_list>
# include <iterator> # include <iterator>
# include <string>
# include <tuple> # include <tuple>
# include <type_traits> # include <type_traits>
# include <utility> # include <utility>
@@ -31,7 +30,7 @@ template <typename T> class is_map {
template <typename> static void check(...); template <typename> static void check(...);
public: public:
static constexpr const bool value = static constexpr bool value =
!std::is_void<decltype(check<T>(nullptr))>::value; !std::is_void<decltype(check<T>(nullptr))>::value;
}; };
@@ -40,17 +39,16 @@ template <typename T> class is_set {
template <typename> static void check(...); template <typename> static void check(...);
public: public:
static constexpr const bool value = static constexpr bool value =
!std::is_void<decltype(check<T>(nullptr))>::value && !is_map<T>::value; !std::is_void<decltype(check<T>(nullptr))>::value && !is_map<T>::value;
}; };
// C array overload // C array overload
template <typename T, std::size_t N> template <typename T, size_t N>
auto range_begin(const T (&arr)[N]) -> const T* { auto range_begin(const T (&arr)[N]) -> const T* {
return arr; return arr;
} }
template <typename T, std::size_t N> template <typename T, size_t N> auto range_end(const T (&arr)[N]) -> const T* {
auto range_end(const T (&arr)[N]) -> const T* {
return arr + N; return arr + N;
} }
@@ -120,7 +118,7 @@ template <typename T> class is_tuple_like_ {
template <typename> static void check(...); template <typename> static void check(...);
public: public:
static constexpr const bool value = static constexpr bool value =
!std::is_void<decltype(check<T>(nullptr))>::value; !std::is_void<decltype(check<T>(nullptr))>::value;
}; };
@@ -154,7 +152,7 @@ using tuple_index_sequence = make_index_sequence<std::tuple_size<T>::value>;
template <typename T, typename C, bool = is_tuple_like_<T>::value> template <typename T, typename C, bool = is_tuple_like_<T>::value>
class is_tuple_formattable_ { class is_tuple_formattable_ {
public: public:
static constexpr const bool value = false; static constexpr bool value = false;
}; };
template <typename T, typename C> class is_tuple_formattable_<T, C, true> { template <typename T, typename C> class is_tuple_formattable_<T, C, true> {
template <size_t... Is> template <size_t... Is>
@@ -170,7 +168,7 @@ template <typename T, typename C> class is_tuple_formattable_<T, C, true> {
C>::value)...>{})); C>::value)...>{}));
public: public:
static constexpr const bool value = static constexpr bool value =
decltype(check(tuple_index_sequence<T>{}))::value; decltype(check(tuple_index_sequence<T>{}))::value;
}; };
@@ -208,7 +206,7 @@ template <typename Char, typename... T>
using result_t = std::tuple<formatter<remove_cvref_t<T>, Char>...>; using result_t = std::tuple<formatter<remove_cvref_t<T>, Char>...>;
using std::get; using std::get;
template <typename Tuple, typename Char, std::size_t... Is> template <typename Tuple, typename Char, size_t... Is>
auto get_formatters(index_sequence<Is...>) auto get_formatters(index_sequence<Is...>)
-> result_t<Char, decltype(get<Is>(std::declval<Tuple>()))...>; -> result_t<Char, decltype(get<Is>(std::declval<Tuple>()))...>;
} // namespace tuple } // namespace tuple
@@ -219,7 +217,7 @@ template <typename R> struct range_reference_type_impl {
using type = decltype(*detail::range_begin(std::declval<R&>())); using type = decltype(*detail::range_begin(std::declval<R&>()));
}; };
template <typename T, std::size_t N> struct range_reference_type_impl<T[N]> { template <typename T, size_t N> struct range_reference_type_impl<T[N]> {
using type = T&; using type = T&;
}; };
@@ -281,14 +279,15 @@ template <typename FormatContext> struct format_tuple_element {
} // namespace detail } // namespace detail
FMT_EXPORT
template <typename T> struct is_tuple_like { template <typename T> struct is_tuple_like {
static constexpr const bool value = static constexpr bool value =
detail::is_tuple_like_<T>::value && !detail::is_range_<T>::value; detail::is_tuple_like_<T>::value && !detail::is_range_<T>::value;
}; };
FMT_EXPORT
template <typename T, typename C> struct is_tuple_formattable { template <typename T, typename C> struct is_tuple_formattable {
static constexpr const bool value = static constexpr bool value = detail::is_tuple_formattable_<T, C>::value;
detail::is_tuple_formattable_<T, C>::value;
}; };
template <typename Tuple, typename Char> template <typename Tuple, typename Char>
@@ -343,8 +342,9 @@ struct formatter<Tuple, Char,
} }
}; };
FMT_EXPORT
template <typename T, typename Char> struct is_range { template <typename T, typename Char> struct is_range {
static constexpr const bool value = static constexpr bool value =
detail::is_range_<T>::value && !detail::has_to_string_view<T>::value; detail::is_range_<T>::value && !detail::has_to_string_view<T>::value;
}; };
@@ -368,6 +368,7 @@ template <typename P1, typename... Pn>
struct conjunction<P1, Pn...> struct conjunction<P1, Pn...>
: conditional_t<bool(P1::value), conjunction<Pn...>, P1> {}; : conditional_t<bool(P1::value), conjunction<Pn...>, P1> {};
FMT_EXPORT
template <typename T, typename Char, typename Enable = void> template <typename T, typename Char, typename Enable = void>
struct range_formatter; struct range_formatter;
@@ -670,7 +671,8 @@ struct formatter<join_view<It, Sentinel, Char>, Char> {
} }
}; };
template <typename Char, typename Tuple> struct tuple_join_view : detail::view { FMT_EXPORT
template <typename Tuple, typename Char> struct tuple_join_view : detail::view {
const Tuple& tuple; const Tuple& tuple;
basic_string_view<Char> sep; basic_string_view<Char> sep;
@@ -685,15 +687,15 @@ template <typename Char, typename Tuple> struct tuple_join_view : detail::view {
# define FMT_TUPLE_JOIN_SPECIFIERS 0 # define FMT_TUPLE_JOIN_SPECIFIERS 0
#endif #endif
template <typename Char, typename Tuple> template <typename Tuple, typename Char>
struct formatter<tuple_join_view<Char, Tuple>, Char, struct formatter<tuple_join_view<Tuple, Char>, Char,
enable_if_t<is_tuple_like<Tuple>::value>> { enable_if_t<is_tuple_like<Tuple>::value>> {
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* { FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
return do_parse(ctx, std::tuple_size<Tuple>()); return do_parse(ctx, std::tuple_size<Tuple>());
} }
template <typename FormatContext> template <typename FormatContext>
auto format(const tuple_join_view<Char, Tuple>& value, auto format(const tuple_join_view<Tuple, Char>& value,
FormatContext& ctx) const -> typename FormatContext::iterator { FormatContext& ctx) const -> typename FormatContext::iterator {
return do_format(value, ctx, std::tuple_size<Tuple>()); return do_format(value, ctx, std::tuple_size<Tuple>());
} }
@@ -725,14 +727,14 @@ struct formatter<tuple_join_view<Char, Tuple>, Char,
} }
template <typename FormatContext> template <typename FormatContext>
auto do_format(const tuple_join_view<Char, Tuple>&, FormatContext& ctx, auto do_format(const tuple_join_view<Tuple, Char>&, FormatContext& ctx,
std::integral_constant<size_t, 0>) const -> std::integral_constant<size_t, 0>) const ->
typename FormatContext::iterator { typename FormatContext::iterator {
return ctx.out(); return ctx.out();
} }
template <typename FormatContext, size_t N> template <typename FormatContext, size_t N>
auto do_format(const tuple_join_view<Char, Tuple>& value, FormatContext& ctx, auto do_format(const tuple_join_view<Tuple, Char>& value, FormatContext& ctx,
std::integral_constant<size_t, N>) const -> std::integral_constant<size_t, N>) const ->
typename FormatContext::iterator { typename FormatContext::iterator {
using std::get; using std::get;
@@ -754,7 +756,7 @@ template <typename T> class is_container_adaptor_like {
template <typename> static void check(...); template <typename> static void check(...);
public: public:
static constexpr const bool value = static constexpr bool value =
!std::is_void<decltype(check<T>(nullptr))>::value; !std::is_void<decltype(check<T>(nullptr))>::value;
}; };
@@ -825,7 +827,7 @@ auto join(Range&& r, string_view sep)
*/ */
template <typename Tuple, FMT_ENABLE_IF(is_tuple_like<Tuple>::value)> template <typename Tuple, FMT_ENABLE_IF(is_tuple_like<Tuple>::value)>
FMT_CONSTEXPR auto join(const Tuple& tuple, string_view sep) FMT_CONSTEXPR auto join(const Tuple& tuple, string_view sep)
-> tuple_join_view<char, Tuple> { -> tuple_join_view<Tuple, char> {
return {tuple, sep}; return {tuple, sep};
} }

View File

@@ -15,15 +15,13 @@
# include <atomic> # include <atomic>
# include <bitset> # include <bitset>
# include <complex> # include <complex>
# include <cstdlib>
# include <exception> # include <exception>
# include <functional> # include <functional> // std::reference_wrapper
# include <memory> # include <memory>
# include <thread> # include <thread>
# include <type_traits> # include <type_traits>
# include <typeinfo> # include <typeinfo> // std::type_info
# include <utility> # include <utility> // std::make_index_sequence
# include <vector>
// Check FMT_CPLUSPLUS to suppress a bogus warning in MSVC. // Check FMT_CPLUSPLUS to suppress a bogus warning in MSVC.
# if FMT_CPLUSPLUS >= 201703L # if FMT_CPLUSPLUS >= 201703L
@@ -62,27 +60,26 @@
# endif # endif
#endif #endif
// For older Xcode versions, __cpp_lib_xxx flags are inaccurately defined. #ifdef FMT_CPP_LIB_FILESYSTEM
#ifndef FMT_CPP_LIB_FILESYSTEM // Use the provided definition.
# ifdef __cpp_lib_filesystem #elif defined(__cpp_lib_filesystem)
# define FMT_CPP_LIB_FILESYSTEM __cpp_lib_filesystem # define FMT_CPP_LIB_FILESYSTEM __cpp_lib_filesystem
# else #else
# define FMT_CPP_LIB_FILESYSTEM 0 # define FMT_CPP_LIB_FILESYSTEM 0
# endif
#endif #endif
#ifndef FMT_CPP_LIB_VARIANT #ifdef FMT_CPP_LIB_VARIANT
# ifdef __cpp_lib_variant // Use the provided definition.
# define FMT_CPP_LIB_VARIANT __cpp_lib_variant #elif defined(__cpp_lib_variant)
# else # define FMT_CPP_LIB_VARIANT __cpp_lib_variant
# define FMT_CPP_LIB_VARIANT 0 #else
# endif # define FMT_CPP_LIB_VARIANT 0
#endif #endif
FMT_BEGIN_NAMESPACE
namespace detail {
#if FMT_CPP_LIB_FILESYSTEM #if FMT_CPP_LIB_FILESYSTEM
FMT_BEGIN_NAMESPACE
namespace detail {
template <typename Char, typename PathChar> template <typename Char, typename PathChar>
auto get_path_string(const std::filesystem::path& p, auto get_path_string(const std::filesystem::path& p,
@@ -111,8 +108,168 @@ void write_escaped_path(basic_memory_buffer<Char>& quoted,
} }
} }
#endif // FMT_CPP_LIB_FILESYSTEM
#if defined(__cpp_lib_expected) || FMT_CPP_LIB_VARIANT
template <typename Char, typename OutputIt, typename T>
auto write_escaped_alternative(OutputIt out, const T& v) -> OutputIt {
if constexpr (has_to_string_view<T>::value)
return write_escaped_string<Char>(out, detail::to_string_view(v));
if constexpr (std::is_same_v<T, Char>) return write_escaped_char(out, v);
return write<Char>(out, v);
}
#endif
#if FMT_CPP_LIB_VARIANT
template <typename> struct is_variant_like_ : std::false_type {};
template <typename... Types>
struct is_variant_like_<std::variant<Types...>> : std::true_type {};
template <typename Variant, typename Char> class is_variant_formattable {
template <size_t... Is>
static auto check(std::index_sequence<Is...>) -> std::conjunction<
is_formattable<std::variant_alternative_t<Is, Variant>, Char>...>;
public:
static constexpr bool value = decltype(check(
std::make_index_sequence<std::variant_size<Variant>::value>()))::value;
};
#endif // FMT_CPP_LIB_VARIANT
#if FMT_USE_RTTI
template <typename OutputIt>
auto write_demangled_name(OutputIt out, const std::type_info& ti) -> OutputIt {
# ifdef FMT_HAS_ABI_CXA_DEMANGLE
int status = 0;
size_t size = 0;
std::unique_ptr<char, void (*)(void*)> demangled_name_ptr(
abi::__cxa_demangle(ti.name(), nullptr, &size, &status), &std::free);
string_view demangled_name_view;
if (demangled_name_ptr) {
demangled_name_view = demangled_name_ptr.get();
// Normalization of stdlib inline namespace names.
// libc++ inline namespaces.
// std::__1::* -> std::*
// std::__1::__fs::* -> std::*
// libstdc++ inline namespaces.
// std::__cxx11::* -> std::*
// std::filesystem::__cxx11::* -> std::filesystem::*
if (demangled_name_view.starts_with("std::")) {
char* begin = demangled_name_ptr.get();
char* to = begin + 5; // std::
for (char *from = to, *end = begin + demangled_name_view.size();
from < end;) {
// This is safe, because demangled_name is NUL-terminated.
if (from[0] == '_' && from[1] == '_') {
char* next = from + 1;
while (next < end && *next != ':') next++;
if (next[0] == ':' && next[1] == ':') {
from = next + 2;
continue;
}
}
*to++ = *from++;
}
demangled_name_view = {begin, detail::to_unsigned(to - begin)};
}
} else {
demangled_name_view = string_view(ti.name());
}
return detail::write_bytes<char>(out, demangled_name_view);
# elif FMT_MSC_VERSION
const string_view demangled_name(ti.name());
for (size_t i = 0; i < demangled_name.size(); ++i) {
auto sub = demangled_name;
sub.remove_prefix(i);
if (sub.starts_with("enum ")) {
i += 4;
continue;
}
if (sub.starts_with("class ") || sub.starts_with("union ")) {
i += 5;
continue;
}
if (sub.starts_with("struct ")) {
i += 6;
continue;
}
if (*sub.begin() != ' ') *out++ = *sub.begin();
}
return out;
# else
return detail::write_bytes<char>(out, string_view(ti.name()));
# endif
}
#endif // FMT_USE_RTTI
template <typename T, typename Enable = void>
struct has_flip : std::false_type {};
template <typename T>
struct has_flip<T, void_t<decltype(std::declval<T>().flip())>>
: std::true_type {};
template <typename T> struct is_bit_reference_like {
static constexpr bool value = std::is_convertible<T, bool>::value &&
std::is_nothrow_assignable<T, bool>::value &&
has_flip<T>::value;
};
// Workaround for libc++ incompatibility with C++ standard.
// According to the Standard, `bitset::operator[] const` returns bool.
#if defined(_LIBCPP_VERSION) && !defined(FMT_IMPORT_STD)
template <typename C>
struct is_bit_reference_like<std::__bit_const_reference<C>> {
static constexpr bool value = true;
};
#endif
template <typename T, typename Enable = void>
struct has_format_as : std::false_type {};
template <typename T>
struct has_format_as<T, void_t<decltype(format_as(std::declval<const T&>()))>>
: std::true_type {};
template <typename T, typename Enable = void>
struct has_format_as_member : std::false_type {};
template <typename T>
struct has_format_as_member<
T, void_t<decltype(formatter<T>::format_as(std::declval<const T&>()))>>
: std::true_type {};
} // namespace detail } // namespace detail
template <typename T, typename Deleter>
auto ptr(const std::unique_ptr<T, Deleter>& p) -> const void* {
return p.get();
}
template <typename T> auto ptr(const std::shared_ptr<T>& p) -> const void* {
return p.get();
}
#if FMT_CPP_LIB_FILESYSTEM
class path : public std::filesystem::path {
public:
auto display_string() const -> std::string {
const std::filesystem::path& base = *this;
return fmt::format(FMT_STRING("{}"), base);
}
auto system_string() const -> std::string { return string(); }
auto generic_display_string() const -> std::string {
const std::filesystem::path& base = *this;
return fmt::format(FMT_STRING("{:g}"), base);
}
auto generic_system_string() const -> std::string { return generic_string(); }
};
template <typename Char> struct formatter<std::filesystem::path, Char> { template <typename Char> struct formatter<std::filesystem::path, Char> {
private: private:
format_specs specs_; format_specs specs_;
@@ -162,39 +319,20 @@ template <typename Char> struct formatter<std::filesystem::path, Char> {
} }
}; };
class path : public std::filesystem::path {
public:
auto display_string() const -> std::string {
const std::filesystem::path& base = *this;
return fmt::format(FMT_STRING("{}"), base);
}
auto system_string() const -> std::string { return string(); }
auto generic_display_string() const -> std::string {
const std::filesystem::path& base = *this;
return fmt::format(FMT_STRING("{:g}"), base);
}
auto generic_system_string() const -> std::string { return generic_string(); }
};
FMT_END_NAMESPACE
#endif // FMT_CPP_LIB_FILESYSTEM #endif // FMT_CPP_LIB_FILESYSTEM
FMT_BEGIN_NAMESPACE template <size_t N, typename Char>
template <std::size_t N, typename Char>
struct formatter<std::bitset<N>, Char> struct formatter<std::bitset<N>, Char>
: nested_formatter<basic_string_view<Char>, Char> { : nested_formatter<basic_string_view<Char>, Char> {
private: private:
// Functor because C++11 doesn't support generic lambdas. // This is a functor because C++11 doesn't support generic lambdas.
struct writer { struct writer {
const std::bitset<N>& bs; const std::bitset<N>& bs;
template <typename OutputIt> template <typename OutputIt>
FMT_CONSTEXPR auto operator()(OutputIt out) -> OutputIt { FMT_CONSTEXPR auto operator()(OutputIt out) -> OutputIt {
for (auto pos = N; pos > 0; --pos) { for (auto pos = N; pos > 0; --pos)
out = detail::write<Char>(out, bs[pos - 1] ? Char('1') : Char('0')); out = detail::write<Char>(out, bs[pos - 1] ? Char('1') : Char('0'));
}
return out; return out;
} }
}; };
@@ -209,10 +347,8 @@ struct formatter<std::bitset<N>, Char>
template <typename Char> template <typename Char>
struct formatter<std::thread::id, Char> : basic_ostream_formatter<Char> {}; struct formatter<std::thread::id, Char> : basic_ostream_formatter<Char> {};
FMT_END_NAMESPACE
#ifdef __cpp_lib_optional #ifdef __cpp_lib_optional
FMT_BEGIN_NAMESPACE
template <typename T, typename Char> template <typename T, typename Char>
struct formatter<std::optional<T>, Char, struct formatter<std::optional<T>, Char,
std::enable_if_t<is_formattable<T, Char>::value>> { std::enable_if_t<is_formattable<T, Char>::value>> {
@@ -251,30 +387,9 @@ struct formatter<std::optional<T>, Char,
return detail::write(out, ')'); return detail::write(out, ')');
} }
}; };
FMT_END_NAMESPACE
#endif // __cpp_lib_optional #endif // __cpp_lib_optional
#if defined(__cpp_lib_expected) || FMT_CPP_LIB_VARIANT
FMT_BEGIN_NAMESPACE
namespace detail {
template <typename Char, typename OutputIt, typename T>
auto write_escaped_alternative(OutputIt out, const T& v) -> OutputIt {
if constexpr (has_to_string_view<T>::value)
return write_escaped_string<Char>(out, detail::to_string_view(v));
if constexpr (std::is_same_v<T, Char>) return write_escaped_char(out, v);
return write<Char>(out, v);
}
} // namespace detail
FMT_END_NAMESPACE
#endif
#ifdef __cpp_lib_expected #ifdef __cpp_lib_expected
FMT_BEGIN_NAMESPACE
template <typename T, typename E, typename Char> template <typename T, typename E, typename Char>
struct formatter<std::expected<T, E>, Char, struct formatter<std::expected<T, E>, Char,
std::enable_if_t<(std::is_void<T>::value || std::enable_if_t<(std::is_void<T>::value ||
@@ -301,11 +416,9 @@ struct formatter<std::expected<T, E>, Char,
return out; return out;
} }
}; };
FMT_END_NAMESPACE
#endif // __cpp_lib_expected #endif // __cpp_lib_expected
#ifdef __cpp_lib_source_location #ifdef __cpp_lib_source_location
FMT_BEGIN_NAMESPACE
template <> struct formatter<std::source_location> { template <> struct formatter<std::source_location> {
FMT_CONSTEXPR auto parse(parse_context<>& ctx) { return ctx.begin(); } FMT_CONSTEXPR auto parse(parse_context<>& ctx) { return ctx.begin(); }
@@ -323,42 +436,12 @@ template <> struct formatter<std::source_location> {
return out; return out;
} }
}; };
FMT_END_NAMESPACE
#endif #endif
#if FMT_CPP_LIB_VARIANT #if FMT_CPP_LIB_VARIANT
FMT_BEGIN_NAMESPACE
namespace detail {
template <typename T>
using variant_index_sequence =
std::make_index_sequence<std::variant_size<T>::value>;
template <typename> struct is_variant_like_ : std::false_type {};
template <typename... Types>
struct is_variant_like_<std::variant<Types...>> : std::true_type {};
// formattable element check.
template <typename T, typename C> class is_variant_formattable_ {
template <std::size_t... Is>
static std::conjunction<
is_formattable<std::variant_alternative_t<Is, T>, C>...>
check(std::index_sequence<Is...>);
public:
static constexpr const bool value =
decltype(check(variant_index_sequence<T>{}))::value;
};
} // namespace detail
template <typename T> struct is_variant_like { template <typename T> struct is_variant_like {
static constexpr const bool value = detail::is_variant_like_<T>::value; static constexpr bool value = detail::is_variant_like_<T>::value;
};
template <typename T, typename C> struct is_variant_formattable {
static constexpr const bool value =
detail::is_variant_formattable_<T, C>::value;
}; };
template <typename Char> struct formatter<std::monostate, Char> { template <typename Char> struct formatter<std::monostate, Char> {
@@ -374,10 +457,10 @@ template <typename Char> struct formatter<std::monostate, Char> {
}; };
template <typename Variant, typename Char> template <typename Variant, typename Char>
struct formatter< struct formatter<Variant, Char,
Variant, Char, std::enable_if_t<std::conjunction_v<
std::enable_if_t<std::conjunction_v< is_variant_like<Variant>,
is_variant_like<Variant>, is_variant_formattable<Variant, Char>>>> { detail::is_variant_formattable<Variant, Char>>>> {
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* { FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
return ctx.begin(); return ctx.begin();
} }
@@ -402,10 +485,9 @@ struct formatter<
return out; return out;
} }
}; };
FMT_END_NAMESPACE
#endif // FMT_CPP_LIB_VARIANT #endif // FMT_CPP_LIB_VARIANT
FMT_BEGIN_NAMESPACE
template <> struct formatter<std::error_code> { template <> struct formatter<std::error_code> {
private: private:
format_specs specs_; format_specs specs_;
@@ -459,101 +541,29 @@ template <> struct formatter<std::error_code> {
}; };
#if FMT_USE_RTTI #if FMT_USE_RTTI
namespace detail { template <> struct formatter<std::type_info> {
template <typename Char, typename OutputIt>
auto write_demangled_name(OutputIt out, const std::type_info& ti) -> OutputIt {
# ifdef FMT_HAS_ABI_CXA_DEMANGLE
int status = 0;
std::size_t size = 0;
std::unique_ptr<char, void (*)(void*)> demangled_name_ptr(
abi::__cxa_demangle(ti.name(), nullptr, &size, &status), &std::free);
string_view demangled_name_view;
if (demangled_name_ptr) {
demangled_name_view = demangled_name_ptr.get();
// Normalization of stdlib inline namespace names.
// libc++ inline namespaces.
// std::__1::* -> std::*
// std::__1::__fs::* -> std::*
// libstdc++ inline namespaces.
// std::__cxx11::* -> std::*
// std::filesystem::__cxx11::* -> std::filesystem::*
if (demangled_name_view.starts_with("std::")) {
char* begin = demangled_name_ptr.get();
char* to = begin + 5; // std::
for (char *from = to, *end = begin + demangled_name_view.size();
from < end;) {
// This is safe, because demangled_name is NUL-terminated.
if (from[0] == '_' && from[1] == '_') {
char* next = from + 1;
while (next < end && *next != ':') next++;
if (next[0] == ':' && next[1] == ':') {
from = next + 2;
continue;
}
}
*to++ = *from++;
}
demangled_name_view = {begin, detail::to_unsigned(to - begin)};
}
} else {
demangled_name_view = string_view(ti.name());
}
return detail::write_bytes<Char>(out, demangled_name_view);
# elif FMT_MSC_VERSION
const string_view demangled_name(ti.name());
for (std::size_t i = 0; i < demangled_name.size(); ++i) {
auto sub = demangled_name;
sub.remove_prefix(i);
if (sub.starts_with("enum ")) {
i += 4;
continue;
}
if (sub.starts_with("class ") || sub.starts_with("union ")) {
i += 5;
continue;
}
if (sub.starts_with("struct ")) {
i += 6;
continue;
}
if (*sub.begin() != ' ') *out++ = *sub.begin();
}
return out;
# else
return detail::write_bytes<Char>(out, string_view(ti.name()));
# endif
}
} // namespace detail
template <typename Char>
struct formatter<std::type_info, Char // DEPRECATED! Mixing code unit types.
> {
public: public:
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* { FMT_CONSTEXPR auto parse(parse_context<>& ctx) -> const char* {
return ctx.begin(); return ctx.begin();
} }
template <typename Context> template <typename Context>
auto format(const std::type_info& ti, Context& ctx) const auto format(const std::type_info& ti, Context& ctx) const
-> decltype(ctx.out()) { -> decltype(ctx.out()) {
return detail::write_demangled_name<Char>(ctx.out(), ti); return detail::write_demangled_name(ctx.out(), ti);
} }
}; };
#endif #endif // FMT_USE_RTTI
template <typename T, typename Char> template <typename T>
struct formatter< struct formatter<
T, Char, // DEPRECATED! Mixing code unit types. T, char,
typename std::enable_if<std::is_base_of<std::exception, T>::value>::type> { typename std::enable_if<std::is_base_of<std::exception, T>::value>::type> {
private: private:
bool with_typename_ = false; bool with_typename_ = false;
public: public:
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* { FMT_CONSTEXPR auto parse(parse_context<>& ctx) -> const char* {
auto it = ctx.begin(); auto it = ctx.begin();
auto end = ctx.end(); auto end = ctx.end();
if (it == end || *it == '}') return it; if (it == end || *it == '}') return it;
@@ -570,43 +580,15 @@ struct formatter<
auto out = ctx.out(); auto out = ctx.out();
#if FMT_USE_RTTI #if FMT_USE_RTTI
if (with_typename_) { if (with_typename_) {
out = detail::write_demangled_name<Char>(out, typeid(ex)); out = detail::write_demangled_name(out, typeid(ex));
*out++ = ':'; *out++ = ':';
*out++ = ' '; *out++ = ' ';
} }
#endif #endif
return detail::write_bytes<Char>(out, string_view(ex.what())); return detail::write_bytes<char>(out, string_view(ex.what()));
} }
}; };
namespace detail {
template <typename T, typename Enable = void>
struct has_flip : std::false_type {};
template <typename T>
struct has_flip<T, void_t<decltype(std::declval<T>().flip())>>
: std::true_type {};
template <typename T> struct is_bit_reference_like {
static constexpr const bool value =
std::is_convertible<T, bool>::value &&
std::is_nothrow_assignable<T, bool>::value && has_flip<T>::value;
};
#ifdef _LIBCPP_VERSION
// Workaround for libc++ incompatibility with C++ standard.
// According to the Standard, `bitset::operator[] const` returns bool.
template <typename C>
struct is_bit_reference_like<std::__bit_const_reference<C>> {
static constexpr const bool value = true;
};
#endif
} // namespace detail
// We can't use std::vector<bool, Allocator>::reference and // We can't use std::vector<bool, Allocator>::reference and
// std::bitset<N>::reference because the compiler can't deduce Allocator and N // std::bitset<N>::reference because the compiler can't deduce Allocator and N
// in partial specialization. // in partial specialization.
@@ -621,14 +603,6 @@ struct formatter<BitRef, Char,
} }
}; };
template <typename T, typename Deleter>
auto ptr(const std::unique_ptr<T, Deleter>& p) -> const void* {
return p.get();
}
template <typename T> auto ptr(const std::shared_ptr<T>& p) -> const void* {
return p.get();
}
template <typename T, typename Char> template <typename T, typename Char>
struct formatter<std::atomic<T>, Char, struct formatter<std::atomic<T>, Char,
enable_if_t<is_formattable<T, Char>::value>> enable_if_t<is_formattable<T, Char>::value>>
@@ -715,7 +689,11 @@ template <typename T, typename Char> struct formatter<std::complex<T>, Char> {
template <typename T, typename Char> template <typename T, typename Char>
struct formatter<std::reference_wrapper<T>, Char, struct formatter<std::reference_wrapper<T>, Char,
enable_if_t<is_formattable<remove_cvref_t<T>, Char>::value>> // Guard against format_as because reference_wrapper is
// implicitly convertible to T&.
enable_if_t<is_formattable<remove_cvref_t<T>, Char>::value &&
!detail::has_format_as<T>::value &&
!detail::has_format_as_member<T>::value>>
: formatter<remove_cvref_t<T>, Char> { : formatter<remove_cvref_t<T>, Char> {
template <typename FormatContext> template <typename FormatContext>
auto format(std::reference_wrapper<T> ref, FormatContext& ctx) const auto format(std::reference_wrapper<T> ref, FormatContext& ctx) const
@@ -725,4 +703,5 @@ struct formatter<std::reference_wrapper<T>, Char,
}; };
FMT_END_NAMESPACE FMT_END_NAMESPACE
#endif // FMT_STD_H_ #endif // FMT_STD_H_

View File

@@ -55,6 +55,16 @@ inline auto write_loc(basic_appender<wchar_t> out, loc_value value,
#endif #endif
return false; return false;
} }
template <typename Char>
void vformat_to(buffer<Char>& buf, basic_string_view<Char> fmt,
basic_format_args<buffered_context<Char>> args,
locale_ref loc = {}) {
static_assert(!std::is_same<Char, char>::value, "");
auto out = basic_appender<Char>(buf);
parse_format_string(
fmt, format_handler<Char>{parse_context<Char>(fmt), {out, args, loc}});
}
} // namespace detail } // namespace detail
FMT_BEGIN_EXPORT FMT_BEGIN_EXPORT
@@ -112,10 +122,6 @@ inline auto runtime(wstring_view s) -> runtime_format_string<wchar_t> {
return {{s}}; return {{s}};
} }
#ifdef __cpp_char8_t
template <> struct is_char<char8_t> : bool_constant<detail::is_utf8_enabled> {};
#endif
template <typename... T> template <typename... T>
constexpr auto make_wformat_args(T&... args) constexpr auto make_wformat_args(T&... args)
-> decltype(fmt::make_format_args<wformat_context>(args...)) { -> decltype(fmt::make_format_args<wformat_context>(args...)) {
@@ -151,13 +157,13 @@ auto join(std::initializer_list<T> list, wstring_view sep)
template <typename Tuple, FMT_ENABLE_IF(is_tuple_like<Tuple>::value)> template <typename Tuple, FMT_ENABLE_IF(is_tuple_like<Tuple>::value)>
auto join(const Tuple& tuple, basic_string_view<wchar_t> sep) auto join(const Tuple& tuple, basic_string_view<wchar_t> sep)
-> tuple_join_view<wchar_t, Tuple> { -> tuple_join_view<Tuple, wchar_t> {
return {tuple, sep}; return {tuple, sep};
} }
template <typename Char, FMT_ENABLE_IF(!std::is_same<Char, char>::value)> template <typename Char, FMT_ENABLE_IF(!std::is_same<Char, char>::value)>
auto vformat(basic_string_view<Char> fmt, auto vformat(basic_string_view<Char> fmt,
typename detail::vformat_args<Char>::type args) basic_format_args<buffered_context<Char>> args)
-> std::basic_string<Char> { -> std::basic_string<Char> {
auto buf = basic_memory_buffer<Char>(); auto buf = basic_memory_buffer<Char>();
detail::vformat_to(buf, fmt, args); detail::vformat_to(buf, fmt, args);
@@ -187,24 +193,20 @@ auto format(const S& fmt, T&&... args) -> std::basic_string<Char> {
fmt::make_format_args<buffered_context<Char>>(args...)); fmt::make_format_args<buffered_context<Char>>(args...));
} }
template <typename Locale, typename S, template <typename S, typename Char = detail::format_string_char_t<S>,
typename Char = detail::format_string_char_t<S>, FMT_ENABLE_IF(detail::is_exotic_char<Char>::value)>
FMT_ENABLE_IF(detail::is_locale<Locale>::value&& inline auto vformat(locale_ref loc, const S& fmt,
detail::is_exotic_char<Char>::value)> basic_format_args<buffered_context<Char>> args)
inline auto vformat(const Locale& loc, const S& fmt,
typename detail::vformat_args<Char>::type args)
-> std::basic_string<Char> { -> std::basic_string<Char> {
auto buf = basic_memory_buffer<Char>(); auto buf = basic_memory_buffer<Char>();
detail::vformat_to(buf, detail::to_string_view(fmt), args, detail::vformat_to(buf, detail::to_string_view(fmt), args, loc);
detail::locale_ref(loc));
return {buf.data(), buf.size()}; return {buf.data(), buf.size()};
} }
template <typename Locale, typename S, typename... T, template <typename S, typename... T,
typename Char = detail::format_string_char_t<S>, typename Char = detail::format_string_char_t<S>,
FMT_ENABLE_IF(detail::is_locale<Locale>::value&& FMT_ENABLE_IF(detail::is_exotic_char<Char>::value)>
detail::is_exotic_char<Char>::value)> inline auto format(locale_ref loc, const S& fmt, T&&... args)
inline auto format(const Locale& loc, const S& fmt, T&&... args)
-> std::basic_string<Char> { -> std::basic_string<Char> {
return vformat(loc, detail::to_string_view(fmt), return vformat(loc, detail::to_string_view(fmt),
fmt::make_format_args<buffered_context<Char>>(args...)); fmt::make_format_args<buffered_context<Char>>(args...));
@@ -215,7 +217,7 @@ template <typename OutputIt, typename S,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&& FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
detail::is_exotic_char<Char>::value)> detail::is_exotic_char<Char>::value)>
auto vformat_to(OutputIt out, const S& fmt, auto vformat_to(OutputIt out, const S& fmt,
typename detail::vformat_args<Char>::type args) -> OutputIt { basic_format_args<buffered_context<Char>> args) -> OutputIt {
auto&& buf = detail::get_buffer<Char>(out); auto&& buf = detail::get_buffer<Char>(out);
detail::vformat_to(buf, detail::to_string_view(fmt), args); detail::vformat_to(buf, detail::to_string_view(fmt), args);
return detail::get_iterator(buf, out); return detail::get_iterator(buf, out);
@@ -231,27 +233,24 @@ inline auto format_to(OutputIt out, const S& fmt, T&&... args) -> OutputIt {
fmt::make_format_args<buffered_context<Char>>(args...)); fmt::make_format_args<buffered_context<Char>>(args...));
} }
template <typename Locale, typename S, typename OutputIt, typename... Args, template <typename S, typename OutputIt, typename... Args,
typename Char = detail::format_string_char_t<S>, typename Char = detail::format_string_char_t<S>,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&& FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
detail::is_locale<Locale>::value&& detail::is_exotic_char<Char>::value)>
detail::is_exotic_char<Char>::value)> inline auto vformat_to(OutputIt out, locale_ref loc, const S& fmt,
inline auto vformat_to(OutputIt out, const Locale& loc, const S& fmt, basic_format_args<buffered_context<Char>> args)
typename detail::vformat_args<Char>::type args)
-> OutputIt { -> OutputIt {
auto&& buf = detail::get_buffer<Char>(out); auto&& buf = detail::get_buffer<Char>(out);
vformat_to(buf, detail::to_string_view(fmt), args, detail::locale_ref(loc)); vformat_to(buf, detail::to_string_view(fmt), args, loc);
return detail::get_iterator(buf, out); return detail::get_iterator(buf, out);
} }
template <typename Locale, typename OutputIt, typename S, typename... T, template <typename OutputIt, typename S, typename... T,
typename Char = detail::format_string_char_t<S>, typename Char = detail::format_string_char_t<S>,
bool enable = detail::is_output_iterator<OutputIt, Char>::value && bool enable = detail::is_output_iterator<OutputIt, Char>::value &&
detail::is_locale<Locale>::value &&
detail::is_exotic_char<Char>::value> detail::is_exotic_char<Char>::value>
inline auto format_to(OutputIt out, const Locale& loc, const S& fmt, inline auto format_to(OutputIt out, locale_ref loc, const S& fmt, T&&... args)
T&&... args) -> -> typename std::enable_if<enable, OutputIt>::type {
typename std::enable_if<enable, OutputIt>::type {
return vformat_to(out, loc, detail::to_string_view(fmt), return vformat_to(out, loc, detail::to_string_view(fmt),
fmt::make_format_args<buffered_context<Char>>(args...)); fmt::make_format_args<buffered_context<Char>>(args...));
} }
@@ -260,7 +259,7 @@ template <typename OutputIt, typename Char, typename... Args,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&& FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
detail::is_exotic_char<Char>::value)> detail::is_exotic_char<Char>::value)>
inline auto vformat_to_n(OutputIt out, size_t n, basic_string_view<Char> fmt, inline auto vformat_to_n(OutputIt out, size_t n, basic_string_view<Char> fmt,
typename detail::vformat_args<Char>::type args) basic_format_args<buffered_context<Char>> args)
-> format_to_n_result<OutputIt> { -> format_to_n_result<OutputIt> {
using traits = detail::fixed_buffer_traits; using traits = detail::fixed_buffer_traits;
auto buf = detail::iterator_buffer<OutputIt, Char, traits>(out, n); auto buf = detail::iterator_buffer<OutputIt, Char, traits>(out, n);
@@ -331,18 +330,6 @@ inline auto format(text_style ts, wformat_string<T...> fmt, T&&... args)
return fmt::vformat(ts, fmt, fmt::make_wformat_args(args...)); return fmt::vformat(ts, fmt, fmt::make_wformat_args(args...));
} }
template <typename... T>
FMT_DEPRECATED void print(std::FILE* f, text_style ts, wformat_string<T...> fmt,
const T&... args) {
vprint(f, ts, fmt, fmt::make_wformat_args(args...));
}
template <typename... T>
FMT_DEPRECATED void print(text_style ts, wformat_string<T...> fmt,
const T&... args) {
return print(stdout, ts, fmt, args...);
}
inline void vprint(std::wostream& os, wstring_view fmt, wformat_args args) { inline void vprint(std::wostream& os, wstring_view fmt, wformat_args args) {
auto buffer = basic_memory_buffer<wchar_t>(); auto buffer = basic_memory_buffer<wchar_t>();
detail::vformat_to(buffer, fmt, args); detail::vformat_to(buffer, fmt, args);

View File

@@ -21,7 +21,11 @@ template <typename Mutex>
class ringbuffer_sink final : public base_sink<Mutex> { class ringbuffer_sink final : public base_sink<Mutex> {
public: public:
explicit ringbuffer_sink(size_t n_items) explicit ringbuffer_sink(size_t n_items)
: q_{n_items} {} : q_{n_items} {
if (n_items == 0) {
throw_spdlog_ex("ringbuffer_sink: n_items cannot be zero");
}
}
std::vector<details::log_msg_buffer> last_raw(size_t lim = 0) { std::vector<details::log_msg_buffer> last_raw(size_t lim = 0) {
std::lock_guard<Mutex> lock(base_sink<Mutex>::mutex_); std::lock_guard<Mutex> lock(base_sink<Mutex>::mutex_);

View File

@@ -31,6 +31,8 @@ namespace sinks {
struct tcp_sink_config { struct tcp_sink_config {
std::string server_host; std::string server_host;
int server_port; int server_port;
int timeout_ms =
0; // The timeout for all 3 major socket operations that is connect, send, and recv
bool lazy_connect = false; // if true connect on first log call instead of on construction bool lazy_connect = false; // if true connect on first log call instead of on construction
tcp_sink_config(std::string host, int port) tcp_sink_config(std::string host, int port)
@@ -44,10 +46,22 @@ public:
// connect to tcp host/port or throw if failed // connect to tcp host/port or throw if failed
// host can be hostname or ip address // host can be hostname or ip address
explicit tcp_sink(const std::string &host,
int port,
int timeout_ms = 0,
bool lazy_connect = false)
: config_{host, port} {
config_.timeout_ms = timeout_ms;
config_.lazy_connect = lazy_connect;
if (!config_.lazy_connect) {
client_.connect(config_.server_host, config_.server_port, config_.timeout_ms);
}
}
explicit tcp_sink(tcp_sink_config sink_config) explicit tcp_sink(tcp_sink_config sink_config)
: config_{std::move(sink_config)} { : config_{std::move(sink_config)} {
if (!config_.lazy_connect) { if (!config_.lazy_connect) {
this->client_.connect(config_.server_host, config_.server_port); client_.connect(config_.server_host, config_.server_port, config_.timeout_ms);
} }
} }
@@ -58,7 +72,7 @@ protected:
spdlog::memory_buf_t formatted; spdlog::memory_buf_t formatted;
spdlog::sinks::base_sink<Mutex>::formatter_->format(msg, formatted); spdlog::sinks::base_sink<Mutex>::formatter_->format(msg, formatted);
if (!client_.is_connected()) { if (!client_.is_connected()) {
client_.connect(config_.server_host, config_.server_port); client_.connect(config_.server_host, config_.server_port, config_.timeout_ms);
} }
client_.send(formatted.data(), formatted.size()); client_.send(formatted.data(), formatted.size());
} }

View File

@@ -4,8 +4,8 @@
#pragma once #pragma once
#define SPDLOG_VER_MAJOR 1 #define SPDLOG_VER_MAJOR 1
#define SPDLOG_VER_MINOR 15 #define SPDLOG_VER_MINOR 16
#define SPDLOG_VER_PATCH 3 #define SPDLOG_VER_PATCH 0
#define SPDLOG_TO_VERSION(major, minor, patch) (major * 10000 + minor * 100 + patch) #define SPDLOG_TO_VERSION(major, minor, patch) (major * 10000 + minor * 100 + patch)
#define SPDLOG_VERSION SPDLOG_TO_VERSION(SPDLOG_VER_MAJOR, SPDLOG_VER_MINOR, SPDLOG_VER_PATCH) #define SPDLOG_VERSION SPDLOG_TO_VERSION(SPDLOG_VER_MAJOR, SPDLOG_VER_MINOR, SPDLOG_VER_PATCH)

View File

@@ -11,36 +11,35 @@
#include <spdlog/fmt/bundled/format-inl.h> #include <spdlog/fmt/bundled/format-inl.h>
FMT_BEGIN_NAMESPACE FMT_BEGIN_NAMESPACE
namespace detail {
template FMT_API auto dragonbox::to_decimal(float x) noexcept -> dragonbox::decimal_fp<float>; #if FMT_USE_LOCALE
template FMT_API auto dragonbox::to_decimal(double x) noexcept -> dragonbox::decimal_fp<double>;
#if FMT_USE_LOCALE
// DEPRECATED! locale_ref in the detail namespace
template FMT_API locale_ref::locale_ref(const std::locale& loc); template FMT_API locale_ref::locale_ref(const std::locale& loc);
template FMT_API auto locale_ref::get<std::locale>() const -> std::locale; template FMT_API auto locale_ref::get<std::locale>() const -> std::locale;
#endif #endif
namespace detail {
template FMT_API auto dragonbox::to_decimal(float x) noexcept
-> dragonbox::decimal_fp<float>;
template FMT_API auto dragonbox::to_decimal(double x) noexcept
-> dragonbox::decimal_fp<double>;
// Explicit instantiations for char. // Explicit instantiations for char.
template FMT_API auto thousands_sep_impl(locale_ref) -> thousands_sep_result<char>; template FMT_API auto thousands_sep_impl(locale_ref)
-> thousands_sep_result<char>;
template FMT_API auto decimal_point_impl(locale_ref) -> char; template FMT_API auto decimal_point_impl(locale_ref) -> char;
// DEPRECATED! // DEPRECATED!
template FMT_API void buffer<char>::append(const char*, const char*); template FMT_API void buffer<char>::append(const char*, const char*);
// DEPRECATED!
template FMT_API void vformat_to(buffer<char>&,
string_view,
typename vformat_args<>::type,
locale_ref);
// Explicit instantiations for wchar_t. // Explicit instantiations for wchar_t.
template FMT_API auto thousands_sep_impl(locale_ref) -> thousands_sep_result<wchar_t>; template FMT_API auto thousands_sep_impl(locale_ref)
-> thousands_sep_result<wchar_t>;
template FMT_API auto decimal_point_impl(locale_ref) -> wchar_t; template FMT_API auto decimal_point_impl(locale_ref) -> wchar_t;
// DEPRECATED!
template FMT_API void buffer<wchar_t>::append(const wchar_t*, const wchar_t*); template FMT_API void buffer<wchar_t>::append(const wchar_t*, const wchar_t*);
} // namespace detail } // namespace detail

View File

@@ -49,7 +49,8 @@ set(SPDLOG_UTESTS_SOURCES
test_time_point.cpp test_time_point.cpp
test_stopwatch.cpp test_stopwatch.cpp
test_circular_q.cpp test_circular_q.cpp
test_bin_to_hex.cpp) test_bin_to_hex.cpp
test_ringbuffer.cpp)
if(NOT SPDLOG_NO_EXCEPTIONS) if(NOT SPDLOG_NO_EXCEPTIONS)
list(APPEND SPDLOG_UTESTS_SOURCES test_errors.cpp) list(APPEND SPDLOG_UTESTS_SOURCES test_errors.cpp)
@@ -74,7 +75,7 @@ function(spdlog_prepare_test test_target spdlog_lib)
elseif(SPDLOG_SANITIZE_THREAD) elseif(SPDLOG_SANITIZE_THREAD)
spdlog_enable_thread_sanitizer(${test_target}) spdlog_enable_thread_sanitizer(${test_target})
endif() endif()
add_test(NAME ${test_target} COMMAND ${test_target}) add_test(NAME ${test_target} COMMAND ${test_target} --order decl)
set_tests_properties(${test_target} PROPERTIES RUN_SERIAL ON) set_tests_properties(${test_target} PROPERTIES RUN_SERIAL ON)
endfunction() endfunction()

52
tests/test_ringbuffer.cpp Normal file
View File

@@ -0,0 +1,52 @@
#include "includes.h"
#include "spdlog/sinks/ringbuffer_sink.h"
TEST_CASE("ringbuffer invalid size", "[ringbuffer]") {
REQUIRE_THROWS_AS(spdlog::sinks::ringbuffer_sink_mt(0), spdlog::spdlog_ex);
}
TEST_CASE("ringbuffer stores formatted messages", "[ringbuffer]") {
spdlog::sinks::ringbuffer_sink_st sink(3);
sink.set_pattern("%v");
sink.log(spdlog::details::log_msg{"test", spdlog::level::info, "msg1"});
sink.log(spdlog::details::log_msg{"test", spdlog::level::info, "msg2"});
sink.log(spdlog::details::log_msg{"test", spdlog::level::info, "msg3"});
auto formatted = sink.last_formatted();
REQUIRE(formatted.size() == 3);
using spdlog::details::os::default_eol;
REQUIRE(formatted[0] == spdlog::fmt_lib::format("msg1{}", default_eol));
REQUIRE(formatted[1] == spdlog::fmt_lib::format("msg2{}", default_eol));
REQUIRE(formatted[2] == spdlog::fmt_lib::format("msg3{}", default_eol));
}
TEST_CASE("ringbuffer overrun keeps last items", "[ringbuffer]") {
spdlog::sinks::ringbuffer_sink_st sink(2);
sink.set_pattern("%v");
sink.log(spdlog::details::log_msg{"test", spdlog::level::info, "first"});
sink.log(spdlog::details::log_msg{"test", spdlog::level::info, "second"});
sink.log(spdlog::details::log_msg{"test", spdlog::level::info, "third"});
auto formatted = sink.last_formatted();
REQUIRE(formatted.size() == 2);
using spdlog::details::os::default_eol;
REQUIRE(formatted[0] == spdlog::fmt_lib::format("second{}", default_eol));
REQUIRE(formatted[1] == spdlog::fmt_lib::format("third{}", default_eol));
}
TEST_CASE("ringbuffer retrieval limit", "[ringbuffer]") {
spdlog::sinks::ringbuffer_sink_st sink(3);
sink.set_pattern("%v");
sink.log(spdlog::details::log_msg{"test", spdlog::level::info, "A"});
sink.log(spdlog::details::log_msg{"test", spdlog::level::info, "B"});
sink.log(spdlog::details::log_msg{"test", spdlog::level::info, "C"});
auto formatted = sink.last_formatted(2);
REQUIRE(formatted.size() == 2);
using spdlog::details::os::default_eol;
REQUIRE(formatted[0] == spdlog::fmt_lib::format("B{}", default_eol));
REQUIRE(formatted[1] == spdlog::fmt_lib::format("C{}", default_eol));
}