Using {fmt} & source_location to create variadic-template-based logging function - c++

I'd like to create a simple log function in C++ which prepend the code location to the log message. I'd like to avoid macros overall as well as the usage of __FILE__ & __LINE__.
Note that the format string is always a compile-time string, and I'd like to have a much computation as possible on compile time (the target machine is a small MCU).
I can use the C++20 source_location feature via experimental/source_location.
I can also use {fmt}.
I started off from this. Currently, I've got the following:
#include <fmt/format.h>
#include <experimental/source_location>
using source_location = std::experimental::source_location;
void vlog(fmt::string_view format, fmt::format_args args)
{
fmt::vprint(format, args);
}
template <typename S, typename... Args>
void log(const S& format, const source_location& location, Args&&... args)
{
vlog(format, fmt::make_args_checked<fmt::string_view, uint32_t, Args...>(format, location.file_name(), location.line(), args...));
}
#define MY_LOG(format, ...) log(FMT_STRING("{},{}, " format), source_location::current(), __VA_ARGS__)
int main() {
MY_LOG("invalid squishiness: {}", 42);
}
Which yields correctly ./example.cpp,20, invalid squishiness: 42.
Looks to me like I'm pretty close. I think what's left is to make the log function take a default argument for source_location (I understand that source_location::current() as a default arguement is a good practice). I'm getting the following error though:
:12:99: error: missing default argument on parameter 'args'
Is this even possible to mix variadic templates and default arguments for parameters? If so, how?
Also, is there a way to prepend the "{},{}, " part to the compile-time format string to yield yet another compile-time string (to be used as format)?

You can do it with a struct that represents the format string and location:
#include <fmt/core.h>
#include <source_location>
struct format_string {
fmt::string_view str;
std::source_location loc;
format_string(
const char* str,
const std::source_location& loc =
std::source_location::current()) : str(str), loc(loc) {}
};
void vlog(const format_string& format, fmt::format_args args) {
const auto& loc = format.loc;
fmt::print("{}:{}: ", loc.file_name(), loc.line());
fmt::vprint(format.str, args);
}
template <typename... Args>
void log(const format_string& format, Args&&... args) {
vlog(format, fmt::make_format_args(args...));
}
int main() {
log("invalid squishiness: {}", 42);
}
This prints:
./example.cpp:26: invalid squishiness: 42
Godbolt: https://godbolt.org/z/4aMKcW

Related

fmt lib check variadic templates arguments with FMT_STRING at compile time [duplicate]

This question already has answers here:
{fmt}: always compile-time check format string in function
(2 answers)
Closed 4 months ago.
I'm creating my own logger implementation. To format the log message I'm using the great FMT lib.
I'd like to check all passed format arguments at compile time using FTM_STRING.
I'm having 2 problems
The following results in a compiler error "call to immediate function is not a constant"
is it possible to combine default arguments (in my case std::source_location loc = std::source_location::current() with variadic templates.
I haven't been able to omit the
std::source_location::current()
in actual function call
LoggerTask::log("foo {}",std::source_location::current(),1);
.
#include <fmt/format.h>
#include <string>
#include <string_view>
#include <source_location>
class LoggerTask
{
public:
template <typename... Args>
static void log(
const std::string_view strMessageToFormat,
const std::source_location loc = std::source_location::current(),
const Args&... args
);
private:
static void log(
std::string& strFormatedDebugMessage,
const std::source_location loc
);
};
template<typename ...Args>
void LoggerTask::log(
const std::string_view strMessageToFormat,
const std::source_location loc,
const Args&... args
)
{
std::string strFormatedMessage = fmt::format(FMT_STRING(strMessageToFormat), args...);
log(strFormatedMessage, loc);
}
void LoggerTask::log(
std::string& strFormatedDebugMessage,
const std::source_location loc
)
{
//write some actual log
}
int main() {
fmt::print("The answer is {}.", 42);
LoggerTask::log("foo {}",std::source_location::current(),1);
}
Godbolt link: https://godbolt.org/z/8cM119WPz
Could you help me out? Thx guys :)
The problem is that you are calling FMT_STRING with a runtime string_view which cannot be checked at compile time.
Moreover FMT_STRING is a legacy API for older compilers. It is recommended to use fmt::format_string instead:
template<typename... Args>
void LoggerTask::log(fmt::format_string<Args...> fmt,
std::source_location loc,
Args&&... args) {
std::string msg = fmt::format(fmt, std::forward<Args>(args)...);
log(msg, loc);
}
See also https://fmt.dev/dev/api.html.

How to create a function that forwards its arguments to fmt::format keeping the type-safeness?

I have two broadly related questions.
I want to make a function that forwards the arguments to fmt::format (and later to std::format, when the support increases). Something like this:
#include <iostream>
#include <fmt/core.h>
constexpr auto my_print(auto&& fmt, auto&&... args) {
// Error here!
// ~~~~~~~~v~~~~~~~~
return fmt::format(fmt, args...);
}
int main() {
std::cout << my_print("{}", 42) << std::endl;
}
Tested with gcc 11.1.0:
In instantiation of ‘constexpr auto my_print(auto:11&&, auto:12&& ...) [with auto:11 = const char (&)[3]; auto:12 = {int}]’:
error: ‘fmt’ is not a constant expression
And tested with clang 12.0.1:
error: call to consteval function 'fmt::basic_format_string<char, int &>::basic_format_string<char [3], 0>' is not a constant expression
In the library (core.h) it's declared something like this:
template <typename... T>
auto format(format_string<T...> fmt, T&&... args) -> std::string {
// ...
}
The problem is that cppreference indicates that the type of the first parameter is unspecified. So
How can I make a function like my_print that passes the arguments to fmt::format and still catches the same kind of errors? Is there a more general way to do this for any kind of function?
How can I infer the type of a parameter of a function like std::format?
For more context, I want to make a function that calls to std::format conditionally, avoiding the formatting at all if the string won't be needed. If you know a better way to make this leave a comment, I'll be very greatful. However, my question about how to solve the general problem still stands.
C++23 may include https://wg21.link/P2508R1, which will expose the format-string type used by std::format. This corresponds to the fmt::format_string type provided in libfmt. Example use might be:
template <typename... Args>
auto my_print(std::format_string<Args...> fmt, Args&&... args) {
return std::format(fmt, std::forward<Args>(args)...);
}
Before C++23, you can use std::vformat / fmt::vformat instead.
template <typename... Args>
auto my_print(std::string_view fmt, Args&&... args) {
return std::vformat(fmt, std::make_format_args(std::forward<Args>(args)...));
}
https://godbolt.org/z/5YnY11vE4
The issue is that std::format (and the latest version of fmt::format) require a constant expression for the first parameter, as you have noticed. This is so that it can provide compile-time errors if the format string does not make sense for the passed-in arguments. Using vformat is the way to get around this.
Obviously this sidesteps the compile-time checking normally done for a format string: any errors with the format string will manifest as runtime errors (exceptions) instead.
I'm not sure if there's any easy way to circumvent this, apart from providing the format string as a template parameter. One attempt may be something like this:
template <std::size_t N>
struct static_string {
char str[N] {};
constexpr static_string(const char (&s)[N]) {
std::ranges::copy(s, str);
}
};
template <static_string fmt, typename... Args>
auto my_print(Args&&... args) {
return std::format(fmt.str, std::forward<Args>(args)...);
}
// used like
my_print<"string: {}">(42);
https://godbolt.org/z/5GW16Eac1
If you really want to pass the parameter using "normal-ish" syntax, you could use a user-defined literal to construct a type that stores the string at compile time:
template <std::size_t N>
struct static_string {
char str[N] {};
constexpr static_string(const char (&s)[N]) {
std::ranges::copy(s, str);
}
};
template <static_string s>
struct format_string {
static constexpr const char* string = s.str;
};
template <static_string s>
constexpr auto operator""_fmt() {
return format_string<s>{};
}
template <typename F, typename... Args>
auto my_print(F, Args&&... args) {
return std::format(F::string, std::forward<Args>(args)...);
}
// used like
my_print("string: {}"_fmt, 42);
https://godbolt.org/z/dx1TGdcM9
It's the call to the constructor of fmt::format_string that needs to be a constant expression, so your function should take the format string as a fmt::format_string instead of a generic type:
template <typename... Args>
std::string my_print(fmt::format_string<Args...> s, Args&&... args)
{
return fmt::format(s, std::forward<Args>(args)...);
}

Overloading between printf functions and fmt functions

I have a class that takes a C printf set of variadic arguments in its constructor, like this:
class Foo {
public:
Foo(const char* str, ...) __attribute__((format(printf, 2, 3)));
Now I want to be able to use the fmt library with this class. If I were willing to change all callers I could switch it something like this:
class Foo {
public:
template<typename Str, typename... Args>
Foo(const Str& str, const Args&... args)
: Foo(str, fmt::make_args_checked<Args...>(str, args...))
{}
private:
Foo(fmt::string_view fmt, fmt::format_args args);
But this class is used in 100's of places and it's not feasible to "change the world". So I want to keep both constructors, but obviously now I need a way to choose between them. I'm not excited about having to add a new dummy parameter or something.
Then I thought, well, I'd also really like to enforce using the FMT_STRING() macro since my printf-style code takes advantage of printf format checking in GCC and clang. So maybe I could do something with that: I could create my own macro, say MYFMT(), that would invoke FMT_STRING() somehow, or at least do the same checking, but then resolve to my own type that could be used to choose a different constructor; something like:
#define MYFMT(_f) ...
class Foo {
public:
Foo(const char* str, ...);
Foo(const MyType& str, ...) ...
So, the usage would be something like:
auto x = Foo("this is a %s string", "printf");
auto y = Foo(MYFMT("this is a {} string"), "fmt");
But I have played with this for a few hours and tried to wrap my head around how the FMT_STRING macro works and what I'd need to do, and I can't come up with anything. Maybe this isn't possible for some reason but if anyone has any hints that would be great.
FWIW my base compilers are GCC 10, clang 9, and MSVC 2019 so I can rely on C++17 at least.
You can do it as follows (godbolt):
#include <fmt/core.h>
struct format_string {
fmt::string_view str;
constexpr operator fmt::string_view() const { return str; }
};
#define MYFMT(s) format_string{s}
class Foo {
public:
template <typename... T>
Foo(const char* str, T&&... args) {
fmt::print("printf\n");
}
template <typename... T>
Foo(fmt::format_string<T...> str, T&&... args) {
fmt::print("fmt\n");
}
};
int main() {
Foo("%d\n", 42); // calls the printf overload
Foo(MYFMT("{}\n"), 42); // calls the fmt overload
}
With C++20 this will give you compile-time checks in {fmt}. Note that varargs are replaced with variadic templates in the printf overload to avoid ambiguity so you won't be able to apply the format attribute. It might be possible to keep varargs by tweaking this solution a bit.
A better option would be to avoid overloading and macros altogether and use a different function instead:
class Foo {
private:
Foo() {}
public:
Foo(const char* str, ...) {
fmt::print("printf\n");
}
template <typename... T>
static Foo format(fmt::format_string<T...> str, T&&... args) {
fmt::print("fmt\n");
return Foo();
}
};

Concatenate const char at compile time when one of them is a function parameter

I'm trying to add a filename prefix to each log string in spdlog.
The Spdlog formatting string looks as this:
Test log {}
Logs are written as this:
spdlog::error("Test log {}", value)
I'm trying to wrap this call and concatenate additional {} before formatting string, so I can pass the prefix of the file there.
static constexpr char * prefixHolder("{} ");
template<typename... Args>
void critical(const char fmt[], Args&&... args) const
{
constexpr auto fullFmt = prefixHolder + fmt; //I can't find any solution for this
spdlog::critical(fullFmt, m_prefix, std::forward<Args>(args)...);
}
Log log("MyClassOrLogger");
log.critical("My format {}", value);
Is it possible to solve this at compile time? I've tried some approaches, but I haven't found any way to make input fmt argument constexpr for the compiler.
C++ 17
Any suggestions or solutions?
Thanks
Parameter value cannot be used for constexpr.
You might turn:
template<typename... Args>
constexpr void critical(const char fmt[], Args&&... args)
into
template <char... cs, typename... Args>
void critical(std::integer_sequence<char, cs...>, Args&&... args)
{
constexpr char fullFmt[] = {'{', '}', ' ', cs... , '\0'};
spdlog::critical(fullFmt, m_prefix, std::forward<Args>(args)...);
}
to allow to have constexpr char array.
ensure-that-char-pointers-always-point-to-the-same-string-literal shows way to create similar sequences from literal string.
In your case, it would be:
template <typename Char, Char... Cs>
constexpr auto operator"" _cs() -> std::integer_sequence<Char, Cs...> {
return {};
}
Usage is then something like:
log.critical("My format {}"_cs, value); // gcc extension
log.critical(MAKE_SEQUENCE("My format {}"), value);
Demo with gcc extension.
Demo with MACRO.
An old-time method using implicit string literal concatenation during compile time, often used in the case of multi-line strings:
#define HELLO_WORLD "Hello" " World" // -> Hello World
# define MULTI_LINE "Hello" \
" World" // -> Hello World
You can use compile time log level macros of spdlog, might be:
#include <stdio.h>
#define SPDLOG_ERROR(...) printf(__VA_ARGS__) // a dummy macro of spdlog
#define YOUR_prefixHolder "{} "
#define YOUR_ERROR1(fmt, ...) SPDLOG_ERROR(YOUR_prefixHolder fmt, ##__VA_ARGS__);
#define YOUR_ERROR2(...) SPDLOG_ERROR(YOUR_prefixHolder __VA_ARGS__);
int main()
{
YOUR_ERROR1("I have %u apples\n", 5); // {} I have 5 apples
YOUR_ERROR2("I have %u apples\n", 5); // {} I have 5 apples
YOUR_ERROR1(5); // error!
SPDLOG_ERROR(5) // 5
}

Passing constexpr const char* to template function?

Im trying to wrap a printf like function while preserving the warning you get for invalid format (MSVC doesn't allow extending this to your own printf functions..).
Example here:
https://godbolt.org/z/na_JOe
#include <string>
#include <utility>
#include <stdio.h>
#pragma warning(error : 4477)
constexpr const char* t = "%s";
template<typename... Args>
void test_tpl(Args&&... args) {
constexpr size_t optimistic_buffer_size = 1024;
static thread_local char optimistic_buffer[optimistic_buffer_size];
int ret_count = _snprintf(optimistic_buffer, optimistic_buffer_size, t, std::forward<Args>(args)...);
}
int main() {
std::string hello;
test_tpl(hello);
return 0;
}
The above code works (fires the warning as it should).
However the issue is to make the format string a parameter while preserving the invalid format warning
The following naive approach is invalid syntax:
template<typename... Args>
void test_tpl(constexpr const char* format, Args&&... args) {
Now this works if the string is constexpr - it feels like this should be easy enough to turn into a parameter (although im aware it may not be) ? but is there some simpel way or trick here I can benefit from ?
If I understand correctly... you want to pass the format as argument mantaining the compiler warning about the wrong format...
The only way I see is pass the format as template parameter.
Something as follows
#include <string>
#include <utility>
#include <cstdio>
constexpr char const t[] = "%s";
template <char const * frmt, typename ... Args>
void test_tpl (Args && ... args)
{
constexpr size_t optimistic_buffer_size = 1024;
static char optimistic_buffer[optimistic_buffer_size];
snprintf(optimistic_buffer, optimistic_buffer_size, frmt,
std::forward<Args>(args)...);
}
int main ()
{
std::string hello;
test_tpl<t>(hello);
}