I am currently working through learning C++'s variadic templates and I wanted to create an example use of them that is less trivial than the one in my book. I decided to create a modification of printf's interface where the type of each argument does not need to be specified. Here is an example:
print(std::cout, "Welcome to this computer program, % son of %!", "bob", "some klingon");
//prints "Welcome to this computer program, bob son of some klingon."
I have also added the ability to escape a %
character (or any other character, like the escape character itself) with a /
.
#include <iostream>
#include <string>
#include <ostream>
namespace printpriv{
using itr = std::string::const_iterator;
template<typename T>
void print(std::ostream &out, itr it, itr end, const T &t){
bool flagged = false;
while(it != end){
if(flagged){
out << *it;
flagged = false;
}else{
if(*it == '/'){
flagged = true;
}else if(*it=='%') {
out << t;
}else{
out << *it;
}
}
++it;
}
}
template<typename T, typename ... Args>
void print(std::ostream &out, itr it, itr end, const T &t, const Args &...args){
bool flagged = false;
while(it != end){
if(flagged){
out << *it;
flagged = false;
}else{
if(*it == '/'){
flagged = true;
}else if(*it=='%') {
out<<t;
print(out, ++it, end, args...);
break;
}else{
out << *it;
}
}
++it;
}
}
}
//to handle case of no arguments
void print(std::ostream &out, const std::string &pattern){
out << pattern;
}
template<typename ... Args>
void print(std::ostream &out, const std::string &pattern, const Args &...args){
printpriv::print(out, pattern.begin(), pattern.end(), args...);
}
int main() {
print(std::cout, "Hello, I am a % and my name is %.\n", "dog", "capybara");
print(std::cout, "There are % % here.\n", 32, std::string("capybaras"));
print(std::cout, "There are no arguments here.\n");
print(std::cout, "I am 80/% sure that this will work.", 10); //at least one argument is needed to prevent calling the version that just passes through the pattern to the output stream
return 0;
}
In this code, I have these primary concerns but I am also interested in a standard code review:
- There is a huge amount of code duplication between the two
privprint::print
overloads to handle parsing the input pattern, bur I don't see any good way to avoid it. - The
privprint
namespace stores two functions that the mainprint
method uses internally in addition to ausing
declaration. This isn't ideal because I consider the content of this namespace to be an implementation detail of theprint
function, so it should not be accessible to the user. Adding "priv" in the name is a decent way to warn other programmers who may work with this code, but something that the compiler enforces would be ideal. My instinct is to put the using declaration and the function templates that are inprivprint
intoprint
's namespace, but C++ does not allow function templates to be in a function's namespace (nor does it seem to allow any templates at all, which breaks all of my usual ways of getting around this function restriction, like a functor type or a lambda). - The implementation here considers a mis-matched number of un-escaped
%
s and arguments to substitute to be undefined behavior, but it would be better if it were a compile-time error when the string is available. I could make it a run-time error, but that would add run-time cost and thus violate C++'s philosophy of preferring undefined behavior to increased run-time cost. Despite this, I don't see any good way of allowing the templates to parse the string at run time. I suspect that this is possible however by making some use ofconstexpr
. This point is why a template-meta-programming tag is on this question.
\
is the escape character, but your code and your unit tests both indicate that/
is the escape character. \$\endgroup\$