Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++11 compile time format string literal construction for invoking printf

What I would like to do is to create:

template<Args... args)>
int println(Args...) {
  // implementation which calls:
  // printf("<string literal format string at compile time>", args...);
  // additional perk would be compile time type checking
  // I expect to provide a format string for each type by some template
  // specialization.
}

I have been analyzing two interesting pieces of work with compile time string literals:

  • Compile time memory aligned string literals

    https://stackoverflow.com/a/22067775/403571

  • 100% constexpr string implementation

    http://sourceforge.net/p/constexprstr/code/HEAD/tree/no-pp-constexpr_string.cpp

Basically I am more or less able to statically infer length of required string literal for format but nothing more as compiler refuses to treat my work as constexpr. Another big problem is when using constexpr strings from the above link the compiler is never able to infer the size of the resulting string.

The more I try to achieve this the more my ignorance outweighs my enthusiasm. I would appreciate any tips and/or code samples that solve some or all of the problems with doing such an implementation.

Note: I am not looking for advice regarding using different forms of logging such as through cout.

Note2: The should not use any std::string as those are runtime

Note3: Generally all the type-safe printfs are using different approaches and I am familar that this could be easily done with multiple calls to printf. I would like to do it in a single call. I also know that the buffer could be incrementally built but this question is about constructing the format string. :)

Update: Basically what part of the code needs to achieve is

constexpr const char* formatString = build_compile_time_format_string(args...);
// build_compile_time_format_string(3, "hi", -3.4)
//     should evaluate to "%d %s %f"
//          or to "%d hi %f"
like image 410
RushPL Avatar asked Jun 10 '14 22:06

RushPL


1 Answers

Simple enough, we'll build a compile time string with a " %d" or whever for each type, concatenate a '\n', and strip the leading space.

To start, we needed a type to use as a compile time string:

template<char...cs> struct compile_time_string 
{static constexpr char str[sizeof...(cs)+1] = {cs...,'\0'};};
template<char...cs>
const char compile_time_string<cs...>::str[sizeof...(cs)+1];

And to prevent intermediate steps from generating pointless buffers, a stringbuilder:

template<char...cs> struct compile_time_stringbuilder 
{typedef compile_time_string<cs...> string;};

//remove leading spaces from stringbuilder
template<char...cs> struct compile_time_stringbuilder<' ', cs...>
{typedef typename compile_time_stringbuilder<cs...>::string string;};

Then, you need functions that take a compile_time_stringbuffer and a type, and return a compile_time_stringbuffer with the " %d" or whatever appended. Since we're dealing with types, I don't even bother defining the functions. Note that my "end" specialization concatenates a '\n' character for you

template<char...cs, class...Ts> 
compile_time_stringbuilder<cs...,'\n'> concatenate_compile_time_format_string(compile_time_stringbuilder<cs...>);

template<char...cs, class...Ts> 
auto concatenate_compile_time_format_string(compile_time_stringbuilder<cs...>,int,Ts...args)
-> decltype(concatenate_compile_time_format_string(compile_time_stringbuilder<cs...,' ','%','d'>(),args...));

template<char...cs, class...Ts> 
auto concatenate_compile_time_format_string(compile_time_stringbuilder<cs...>,const char*,Ts...args)
-> decltype(concatenate_compile_time_format_string(compile_time_stringbuilder<cs...,' ','%','s'>(),args...));

template<char...cs, class...Ts> 
auto concatenate_compile_time_format_string(compile_time_stringbuilder<cs...>,double,Ts...args)
-> decltype(concatenate_compile_time_format_string(compile_time_stringbuilder<cs...,' ','%','f'>(),args...));

Finally, a helpful, easy to use interface.

template<class...Ts>
constexpr const char* build_compile_time_format_string()
{
    using compile_time_stringbuilder = decltype(concatenate_compile_time_format_string(compile_time_stringbuilder<>(),std::declval<Ts>()...));
    using compile_time_string = typename compile_time_stringbuilder::string;
    return compile_time_string::str;
}

And it's used like so:

template<class...Args>
void println(Args...args) {
    constexpr const char* formatString = build_compile_time_format_string<Args...>();
    std::cout << formatString;
}

Here's proof of execution: http://coliru.stacked-crooked.com/a/16dc0becd3391aaa


Completely unnecessarily, it might be fun to flesh out compile_time_string to roughly match the interface of const std::string, along these lines:

template<char...cs> struct compile_time_string 
{
    static constexpr char str[sizeof...(cs)+1] = {cs...,'\0'};
    constexpr size_t size() {return sizeof...(cs);}
    constexpr char* begin() {return str;}
    constexpr char* end() {return str+sizeof...(cs);}
};
like image 195
Mooing Duck Avatar answered Oct 17 '22 03:10

Mooing Duck