I'm writing a lightweight parsing library for embedded systems and try to avoid iostream. What I want to do is write variables to a buffer like vsnprintf() but I don't want to specify the format string, much rather the format string should be infered from the arguments passed to my variadic template wrapper. Here's an example:
#include <cstddef>
#include <string>
#include <cstdio>
template <typename... Ts>
void cpp_vsnprintf(char* s, size_t n, Ts&&... arg)
{
std::vsnprintf(s, n, /*how to deduce format string?*/, arg...);
}
int main()
{
char buf[100];
cpp_vsnprintf(buf, 100, "hello", 3, '2', 2.4);
printf(buf);
}
I'm looking for a performant solution, maybe the format string could be composed at compile time? Or is there maybe an stl function that does exactly what I'm asking for?
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);
}
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