Skip to content

Commit

Permalink
support std::string with custom allocator
Browse files Browse the repository at this point in the history
  • Loading branch information
odygrd authored Aug 16, 2024
1 parent 478d894 commit 29254e6
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 6 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,8 @@
- Simplified the log tags API. The `Tags` class has been removed. You now pass a `char const*` directly to the macros. Additionally, macros previously named `WITH_TAGS` have been renamed to `_TAGS`. For example, `LOG_INFO_WITH_TAGS` is now `LOG_INFO_TAGS`.
- Renamed `backend_cpu_affinity` to `cpu_affinity` in `BackendOptions` to improve consistency.
- Simplified project structure by removing the extra quill directory and made minor CMake improvements; `include/quill` is now directly in the root.

- Added support for `std::string` with custom allocator ([#524](https://github.com/odygrd/quill/issues/524))

## v6.1.2

- Fix pkg-config file on windows
Expand Down
19 changes: 15 additions & 4 deletions include/quill/core/Codec.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,17 @@ constexpr auto strnlen =
::strnlen
#endif
;

/** std string detection, ignoring the Allocator type **/
template <typename T>
struct is_std_string : std::false_type
{
};

template <typename Allocator>
struct is_std_string<std::basic_string<char, std::char_traits<char>, Allocator>> : std::true_type
{
};
} // namespace detail

/** typename = void for specializations with enable_if **/
Expand All @@ -75,7 +86,7 @@ struct Codec
conditional_arg_size_cache.push_back(static_cast<size_t>(strlen(arg) + 1u));
return conditional_arg_size_cache.back();
}
else if constexpr (std::disjunction_v<std::is_same<Arg, std::string>, std::is_same<Arg, std::string_view>>)
else if constexpr (std::disjunction_v<detail::is_std_string<Arg>, std::is_same<Arg, std::string_view>>)
{
// for std::string we also need to store the size in order to correctly retrieve it
// the reason for this is that if we create e.g:
Expand Down Expand Up @@ -126,7 +137,7 @@ struct Codec
std::memcpy(buffer, arg, len);
buffer += len;
}
else if constexpr (std::disjunction_v<std::is_same<Arg, std::string>, std::is_same<Arg, std::string_view>>)
else if constexpr (std::disjunction_v<detail::is_std_string<Arg>, std::is_same<Arg, std::string_view>>)
{
// for std::string we store the size first, in order to correctly retrieve it
// Copy the length first and then the actual string
Expand Down Expand Up @@ -170,7 +181,7 @@ struct Codec
buffer += strlen(arg) + 1; // for c_strings we add +1 to the length as we also want to copy the null terminated char
return arg;
}
else if constexpr (std::disjunction_v<std::is_same<Arg, std::string>, std::is_same<Arg, std::string_view>>)
else if constexpr (std::disjunction_v<detail::is_std_string<Arg>, std::is_same<Arg, std::string_view>>)
{
// for std::string we first need to retrieve the length
size_t len;
Expand Down Expand Up @@ -204,7 +215,7 @@ struct Codec
// for std::string_view we would need fmt/format.h
args_store->push_back(fmtquill::string_view{arg, strlen(arg)});
}
else if constexpr (std::disjunction_v<std::is_same<Arg, std::string>, std::is_same<Arg, std::string_view>>)
else if constexpr (std::disjunction_v<detail::is_std_string<Arg>, std::is_same<Arg, std::string_view>>)
{
std::string_view arg = decode_arg(buffer);

Expand Down
45 changes: 44 additions & 1 deletion test/integration_tests/StringLoggingTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,49 @@
#include "quill/sinks/FileSink.h"

#include <cstdio>
#include <memory>
#include <string>
#include <string_view>
#include <vector>

using namespace quill;

template <typename T>
class CustomAllocator : public std::allocator<T>
{
public:
using value_type = T;

CustomAllocator() noexcept : std::allocator<T>() {}

template <typename U>
explicit CustomAllocator(const CustomAllocator<U>& other) noexcept : std::allocator<T>(other)
{
}

[[nodiscard]] T* allocate(std::size_t n) { return std::allocator<T>::allocate(n); }

void deallocate(T* p, std::size_t n) noexcept { std::allocator<T>::deallocate(p, n); }

template <typename U>
struct rebind
{
using other = CustomAllocator<U>;
};
};

template <typename T, typename U>
bool operator==(const CustomAllocator<T>&, const CustomAllocator<U>&)
{
return true;
}

template <typename T, typename U>
bool operator!=(const CustomAllocator<T>&, const CustomAllocator<U>&)
{
return false;
}

/***/
TEST_CASE("string_logging")
{
Expand All @@ -39,6 +76,8 @@ TEST_CASE("string_logging")
Logger* logger = Frontend::create_or_get_logger(logger_name, std::move(file_sink));

{
std::basic_string<char, std::char_traits<char>, CustomAllocator<char>> cas =
"custom allocator string";
std::string s = "adipiscing";
std::string const& scr = s;
std::string& sr = s;
Expand All @@ -62,6 +101,7 @@ TEST_CASE("string_logging")
const char* npcs = "Example\u0003String\u0004";
LOG_INFO(logger, "non printable cs [{}]", npcs);

LOG_INFO(logger, "cas [{}]", cas);
LOG_INFO(logger, "s [{}]", s);
LOG_INFO(logger, "scr [{}]", scr);
LOG_INFO(logger, "sr [{}]", sr);
Expand Down Expand Up @@ -110,7 +150,10 @@ TEST_CASE("string_logging")

// Read file and check
std::vector<std::string> const file_contents = quill::testing::file_contents(filename);
REQUIRE_EQ(file_contents.size(), number_of_messages + 15);
REQUIRE_EQ(file_contents.size(), number_of_messages + 16);

REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " cas [custom allocator string]"}));

REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " non printable cs [Example\\x03String\\x04]"}));
Expand Down

0 comments on commit 29254e6

Please sign in to comment.