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);
}
Related
Given the following code:
#include <algorithm>
#include <cstddef>
#include <iterator>
#include <source_location>
template<std::size_t N>
struct StringWrapper {
// consteval to force only compile-time strings
consteval StringWrapper(const char (&format_string)[N], std::source_location source_location = std::source_location::current())
: SourceInfo{ source_location } {
std::copy(std::begin(format_string), std::end(format_string), std::begin(FormatString));
}
char FormatString[N]{};
// This here to motivate not using StringWrapper as non-type template argument
std::source_location SourceInfo{};
};
// class-template-argument-deduction is not necessary here, see the first case in main
// variadic template, thus putting std::source_location at the end is not an option
template<std::size_t N, class... Args>
void log(StringWrapper<N> format_string, Args&&... args);
int main()
{
log(StringWrapper{ "This {}, explicitly instantiating, but deducing the parameter" }, "works");
log("This {}, could not match 'StringWrapper<N>' against 'const char *'", "fails");
}
What could be a possible workaround to this issue that would not require a change on the callsite?
One possibility is to use a std::string_view, however that would not give me access to N as a constant expression in the implementation of log and is thus not an option in my case.
Another possibility is to directly take a const char (&)[N], however then I am losing the std::source_location which is not an option either.
I understand why overload resolution would favor a function that takes a const char* over a function that takes a const char (&)[N], however I do not understand why 'StringWrapper' is not even attempted to match against 'const char (&)[N]' however.
you can use class template argument deduction to do this (change log to object constructor instead of normal function)
template<std::size_t N, class... Args>
struct log{
log(StringWrapper<N> src, Args&&... args){}
};
template<std::size_t N, class... Args>
log(const char(&)[N],Args&&...) -> log<N,Args...>;
template<std::size_t N, class... Args>
log(StringWrapper<N>,Args&&...) -> log<N,Args...>;
Although you said you somehow need N inside log as constant expression, just in case someone reach here don't need it.
Change StringWrapper to non-template can also solve the problem.
struct StringWrapper {
template<std::size_t N>
consteval StringWrapper(const char(&format_string)[N], std::source_location source_location = std::source_location::current())
: FormatString{format_string}, SourceInfo{ source_location }{}
std::string_view FormatString;
std::source_location SourceInfo{};
};
template<typename ... Args>
void log(StringWrapper src, Args&&...){
auto N = src.FormatString.size(); // N, but not constant expression
}
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.
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)...);
}
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
We need to format strings all the time. It would be so nice to be able to say:
std::string formattedStr = format("%s_%06d.dat", "myfile", 18); // myfile_000018.dat
Is there a C++ way of doing this? Some alternatives I considered:
snprintf: uses raw char buffers. Not nice in modern C++ code.
std::stringstream: does not support format pattern strings, instead you must push clumsy iomanip objects into the stream.
boost::format: uses an ad-hoc operator overload of % to specify the arguments. Ugly.
Isn't there a better way with variadic templates now that we have C++11?
It can certainly be written in C++11 with variadic templates. It's best to wrap something that already exists than to try to write the whole thing yourself. If you are already using Boost, it's quite simple to wrap boost::format like this:
#include <boost/format.hpp>
#include <string>
namespace details
{
boost::format& formatImpl(boost::format& f)
{
return f;
}
template <typename Head, typename... Tail>
boost::format& formatImpl(
boost::format& f,
Head const& head,
Tail&&... tail)
{
return formatImpl(f % head, std::forward<Tail>(tail)...);
}
}
template <typename... Args>
std::string format(
std::string formatString,
Args&&... args)
{
boost::format f(std::move(formatString));
return details::formatImpl(f, std::forward<Args>(args)...).str();
}
You can use this the way you wanted:
std::string formattedStr = format("%s_%06d.dat", "myfile", 18); // myfile_000018.dat
If you don't want to use Boost (but you really should) then you can also wrap snprintf. It is a bit more involved and error-prone, since we need to manage char buffers and the old style non-type-safe variable length argument list. It gets a bit cleaner by using unique_ptr's:
#include <cstdio> // snprintf
#include <string>
#include <stdexcept> // runtime_error
#include <memory> // unique_ptr
namespace details
{
template <typename... Args>
std::unique_ptr<char[]> formatImplS(
size_t bufSizeGuess,
char const* formatCStr,
Args&&... args)
{
std::unique_ptr<char[]> buf(new char[bufSizeGuess]);
size_t expandedStrLen = std::snprintf(buf.get(), bufSizeGuess, formatCStr, args...);
if (expandedStrLen >= 0 && expandedStrLen < bufSizeGuess)
{
return buf;
} else if (expandedStrLen >= 0
&& expandedStrLen < std::numeric_limits<size_t>::max())
{
// buffer was too small, redo with the correct size
return formatImplS(expandedStrLen+1, formatCStr, std::forward<Args>(args)...);
} else {
throw std::runtime_error("snprintf failed with return value: "+std::to_string(expandedStrLen));
}
}
char const* ifStringThenConvertToCharBuf(std::string const& cpp)
{
return cpp.c_str();
}
template <typename T>
T ifStringThenConvertToCharBuf(T const& t)
{
return t;
}
}
template <typename... Args>
std::string formatS(std::string const& formatString, Args&&... args)
{
// unique_ptr<char[]> calls delete[] on destruction
std::unique_ptr<char[]> chars = details::formatImplS(4096, formatString.c_str(),
details::ifStringThenConvertToCharBuf(args)...);
// string constructor copies the data
return std::string(chars.get());
}
There are some differences between snprintf and boost::format in terms of format specification but your example works with both.
The fmt library implements exactly that, string formatting using variadic templates. Example:
// printf syntax:
std::string formattedStr = fmt::sprintf("%s_%06d.dat", "myfile", 18);
// Python-like syntax:
std::string formattedStr = fmt::format("{}_{:06}.dat", "myfile", 18);
Disclaimer: I'm the author of the library.