diff --git a/include/slimlog/format-inl.h b/include/slimlog/format-inl.h new file mode 100644 index 0000000..e7a9b34 --- /dev/null +++ b/include/slimlog/format-inl.h @@ -0,0 +1,103 @@ +/** + * @file format-inl.h + * @brief Contains definitions of FormatValue and CachedFormatter classes. + */ + +#pragma once + +// IWYU pragma: private, include + +#ifndef SLIMLOG_HEADER_ONLY +#include +#endif + +#ifdef SLIMLOG_FMTLIB +#if __has_include() +#include +#else +#include +#endif + +#include +#include +#else +#include +#endif + +#include +#include +#include + +namespace SlimLog { + +#ifndef SLIMLOG_FMTLIB +template Char> +FormatValue::FormatValue(const CachedFormatter& formatter, T value) + : m_formatter(formatter) + , m_value(std::move(value)) +{ +} + +template Char> +template +auto FormatValue::format(Context& context) const +{ + return m_formatter.format(m_value, context); +} +#endif + +template Char> +CachedFormatter::CachedFormatter(std::basic_string_view fmt) +#ifdef SLIMLOG_FMTLIB + : m_empty(fmt.empty()) +#endif +{ + FormatParseContext parse_context(std::move(fmt)); + // Suppress buggy GCC warning on fmtlib sources +#if defined(__GNUC__) and not defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wstringop-overflow" +#endif + Formatter::parse(parse_context); +#if defined(__GNUC__) and not defined(__clang__) +#pragma GCC diagnostic pop +#endif +} + +template Char> +template +void CachedFormatter::format(Out& out, T value) const +{ + if (!m_value || value != *m_value) [[unlikely]] { + m_value = std::move(value); + m_buffer.clear(); +#ifdef SLIMLOG_FMTLIB + // Shortcut for numeric types without formatting + if constexpr (std::is_arithmetic_v) { + if (m_empty) [[likely]] { + m_buffer.append(fmt::format_int(*m_value)); + out.append(m_buffer); + return; + } + } + + // For libfmt it's possible to create a custom fmt::basic_format_context + // appending to the buffer directly, which is the most efficient way. + using Appender = std::conditional_t< + std::is_same_v, + fmt::appender, + std::back_insert_iterator>; + fmt::basic_format_context fmt_context(Appender(m_buffer), {}); + Formatter::format(*m_value, fmt_context); +#else + // For std::format there is no way to build a custom format context, + // so we have to use dummy format string (empty string will be omitted), + // and pass FormatValue with a reference to CachedFormatter as an argument. + static constexpr std::array Fmt{'{', '}', '\0'}; + m_buffer.vformat(Fmt.data(), m_buffer.make_format_args(FormatValue(*this, *m_value))); +#endif + } + out.append(m_buffer); +} + +} // namespace SlimLog diff --git a/include/slimlog/format.h b/include/slimlog/format.h index 5bef956..454c37c 100644 --- a/include/slimlog/format.h +++ b/include/slimlog/format.h @@ -1,6 +1,6 @@ /** * @file format.h - * @brief Contains definitions of FormatString, FormatBuffer, and Format classes. + * @brief Contains declarations and definitions of classes related to formatting. */ #pragma once @@ -17,13 +17,13 @@ #else #include -#include #include #endif #include #include +#include #include #include #include @@ -142,60 +142,6 @@ concept Formattable = requires(const Char* fmt, Args... args) { #endif }; -#ifndef SLIMLOG_FMTLIB -template Char> -class CachedFormatter; - -/** - * @brief Wrapper for formatting a value with a cached formatter. - * - * Stores the value and reference to the cached formatter (see CachedFormatter). - * Used in combination with `std::formatter, Char>` specialization. - * - * @tparam T Value type. - * @tparam Char Output character type. - */ -template Char> -class FormatValue final { -public: - /** - * @brief Constructs a new FormatValue object from a reference to the formatter and value. - * - * @param formatter Reference to the formatter. - * @param value Value to be formatted. - */ - constexpr explicit FormatValue(const CachedFormatter& formatter, T value) - : m_formatter(formatter) - , m_value(std::move(value)) - { - } - - ~FormatValue() = default; - - FormatValue(const FormatValue&) = delete; - FormatValue(FormatValue&&) = delete; - auto operator=(const FormatValue&) -> FormatValue& = delete; - auto operator=(FormatValue&&) noexcept -> FormatValue& = delete; - - /** - * @brief Formats the value with the cached formatter. - * - * @tparam Context Format context type (`std::format_context`). - * @param context Format context. - * @return Output iterator to the resulting buffer. - */ - template - auto format(Context& context) const - { - return m_formatter.format(m_value, context); - } - -private: - const CachedFormatter& m_formatter; - T m_value; -}; -#endif - /** * @brief Buffer used for log message formatting. * @@ -288,6 +234,52 @@ class FormatBuffer final : public Util::MemoryBuffer Char> +class CachedFormatter; + +/** + * @brief Wrapper for formatting a value with a cached formatter. + * + * Stores the value and reference to the cached formatter (see CachedFormatter). + * Used in combination with `std::formatter, Char>` specialization. + * + * @tparam T Value type. + * @tparam Char Output character type. + */ +template Char> +class FormatValue final { +public: + /** + * @brief Constructs a new FormatValue object from a reference to the formatter and value. + * + * @param formatter Reference to the formatter. + * @param value Value to be formatted. + */ + explicit FormatValue(const CachedFormatter& formatter, T value); + ~FormatValue() = default; + + FormatValue(const FormatValue&) = delete; + FormatValue(FormatValue&&) = delete; + auto operator=(const FormatValue&) -> FormatValue& = delete; + auto operator=(FormatValue&&) noexcept -> FormatValue& = delete; + + /** + * @brief Formats the value with the cached formatter. + * + * @tparam Context Format context type (`std::format_context`). + * @param context Format context. + * @return Output iterator to the resulting buffer. + */ + template + auto format(Context& context) const; + +private: + const CachedFormatter& m_formatter; + T m_value; +}; +#endif + /** * @brief Wrapper to parse the format string and store the format context. * @@ -301,30 +293,12 @@ class CachedFormatter final : Formatter { public: using Formatter::format; - /** @brief Default size of formatted string (enough for numbers and date/time) */ - constexpr static size_t CachedBufferSize = 20; - /** * @brief Constructs a new CachedFormatter object from a format string. * * @param fmt Format string. */ - constexpr explicit CachedFormatter(std::basic_string_view fmt) -#ifdef SLIMLOG_FMTLIB - : m_empty(fmt.empty()) -#endif - { - FormatParseContext parse_context(std::move(fmt)); - // Suppress buggy GCC warning on fmtlib sources -#if defined(__GNUC__) and not defined(__clang__) -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wstringop-overflow" -#endif - Formatter::parse(parse_context); -#if defined(__GNUC__) and not defined(__clang__) -#pragma GCC diagnostic pop -#endif - } + explicit CachedFormatter(std::basic_string_view fmt); /** * @brief Formats the value and writes to the output buffer. @@ -334,46 +308,14 @@ class CachedFormatter final : Formatter { * @param value Value to be formatted. */ template - void format(Out& out, T value) const - { - if (!m_value || value != *m_value) [[unlikely]] { - m_value = std::move(value); - m_buffer.clear(); -#ifdef SLIMLOG_FMTLIB - // Shortcut for numeric types without formatting - if constexpr (std::is_arithmetic_v) { - if (m_empty) [[likely]] { - m_buffer.append(fmt::format_int(*m_value)); - out.append(m_buffer); - return; - } - } - - // For libfmt it's possible to create a custom fmt::basic_format_context - // appending to the buffer directly, which is the most efficient way. - using Appender = std::conditional_t< - std::is_same_v, - fmt::appender, - std::back_insert_iterator>; - fmt::basic_format_context fmt_context(Appender(m_buffer), {}); - Formatter::format(*m_value, fmt_context); -#else - // For std::format there is no way to build a custom format context, - // so we have to use dummy format string (empty string will be omitted), - // and pass FormatValue with a reference to CachedFormatter as an argument. - static constexpr std::array Fmt{'{', '}', '\0'}; - m_buffer.vformat(Fmt.data(), m_buffer.make_format_args(FormatValue(*this, *m_value))); -#endif - } - out.append(m_buffer); - } + void format(Out& out, T value) const; private: #ifdef SLIMLOG_FMTLIB bool m_empty; #endif mutable std::optional m_value; - mutable FormatBuffer m_buffer; + mutable FormatBuffer m_buffer; }; } // namespace SlimLog @@ -395,3 +337,7 @@ struct std::formatter, Char> { // NOLINT (cert-dcl }; /** @endcond */ #endif + +#ifdef SLIMLOG_HEADER_ONLY +#include // IWYU pragma: keep +#endif diff --git a/src/slimlog.cpp b/src/slimlog.cpp index 458a4d5..1c06c93 100644 --- a/src/slimlog.cpp +++ b/src/slimlog.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -6,12 +7,15 @@ #ifndef SLIMLOG_HEADER_ONLY // IWYU pragma: begin_keep +#include #include #include #include // IWYU pragma: end_keep #endif +#include +#include #include namespace SlimLog { @@ -21,6 +25,12 @@ template class SinkDriver, MultiThreadedPolicy>; template class Sink>; template class RecordStringView; template class Pattern; +template class CachedFormatter; +template class CachedFormatter; +#ifndef SLIMLOG_FMTLIB +template class FormatValue; +template class FormatValue; +#endif // wchar_t template class SinkDriver, SingleThreadedPolicy>; @@ -28,6 +38,12 @@ template class SinkDriver, MultiThreadedPolicy>; template class Sink>; template class RecordStringView; template class Pattern; +template class CachedFormatter; +template class CachedFormatter; +#ifndef SLIMLOG_FMTLIB +template class FormatValue; +template class FormatValue; +#endif // char8_t (std::mbrtoc8() is supported only for newer versions of glibc++ and libc++) /*#if defined(__cpp_char8_t)