Reputation: 101041
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.
Upvotes: 4
Views: 668
Reputation: 55665
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();
}
};
Upvotes: 1