I figured it out with some inspiration from this thread
#include <cstdio>
template<class T> struct format;
template<class T> struct format<T*> { static constexpr char const * spec = "%p"; };
template<> struct format<int> { static constexpr char const * spec = "%d"; };
template<> struct format<double> { static constexpr char const * spec = "%.2f";};
template<> struct format<const char*> { static constexpr char const * spec = "%s"; };
template<> struct format<char> { static constexpr char const * spec = "%c"; };
template<> struct format<unsigned long> { static constexpr char const * spec = "%lu"; };
template <typename... Ts>
class cxpr_string
{
public:
constexpr cxpr_string() : buf_{}, size_{0} {
size_t i=0;
( [&]() {
const size_t max = size(format<Ts>::spec);
for (int i=0; i < max; ++i) {
buf_[size_++] = format<Ts>::spec[i];
}
}(), ...);
buf_[size_++] = 0;
}
static constexpr size_t size(const char* s)
{
size_t i=0;
for (; *s != 0; ++s) ++i;
return i;
}
template <typename... Is>
static constexpr size_t calc_size() {
return (0 + ... + size(format<Is>::spec));
}
constexpr const char* get() const {
return buf_;
}
static constexpr cxpr_string<Ts...> ref{};
static constexpr const char* value = ref.get();
private:
char buf_[calc_size<Ts...>()+1] = { 0 };
size_t size_;
};
template <typename... Ts>
auto cpp_vsnprintf(char* s, size_t n, Ts... arg)
{
return snprintf(s, n, cxpr_string<Ts...>::value, arg...);
}
int main()
{
char buf[100];
cpp_vsnprintf(buf, 100, "my R", 2, 'D', 2, '=', 3.5);
printf(buf);
}
Demo
Output:
my R2D2=3.50
You can see that format strings are neatly packed into the binary:
.string "%s%d%c%d%c%.2f"
.zero 1
.quad 15