I've had a new enthusiasm for exploring the new features in C++11 since the standard was accepted.
I've come across this before which shows how variadic templates can be used to make a type safe printf for C++. However, I prefer using std::cout as it is an ostream so I can use exactly same syntax when I want to do output for other ostreams.
I came up with a system that uses this syntax:
Code:
std::cout << Format("three: %, a letter: %\n")(3, 'c');
which has the output:
Code:
three: 3, a letter: c
This also allows writing formatters like this:
Code:
Format dollarFmt("$%.00");
std::cout << dollarFmt(187) << std::endl;
Here's the implementation:
Code:
#include <algorithm>
#include <stdexcept>
#include <cstddef>
#include <ostream>
#include <tuple>
template <typename Tuple, std::size_t N>
struct TuplePrinter
{
void operator()(std::ostream& os, const char* s, const Tuple& args) const
{
while (*s)
{
if (*s == '%')
{
os << std::get<std::tuple_size<Tuple>::value - N>(args);
++s;
TuplePrinter<Tuple, N - 1>()(os, s, args);
return;
}
os << *s++;
}
throw std::logic_error("Too many arguments provided to Format.");
}
};
template <typename Tuple>
struct TuplePrinter<Tuple, static_cast<std::size_t>(0)>
{
void operator()(std::ostream& os, const char* s, const Tuple& args) const
{
while (*s)
{
if (*s == '%')
throw std::runtime_error("Not enough arguments provided to Format.");
os << *s++;
}
}
};
template <typename... Args>
class FormatOutput
{
private:
typedef std::tuple<Args...> Tuple;
const char* mFormatString;
const Tuple mTuple;
public:
FormatOutput(const char* formatString, Args... args)
:
mFormatString(formatString),
mTuple(args...)
{
}
void operator()(std::ostream& os) const
{
TuplePrinter<Tuple, std::tuple_size<Tuple>::value>()(os, mFormatString, mTuple);
}
};
template <typename... Args>
std::ostream& operator<<(std::ostream& os, const FormatOutput<Args...>& formatOutput)
{
formatOutput(os);
return os;
}
class Format
{
private:
const char* mFormatString;
public:
Format(const char* formatString)
:
mFormatString(formatString)
{
}
Format(char*) = delete;
template <typename... Args>
FormatOutput<const Args&...> operator()(const Args&... args) const
{
return FormatOutput<const Args&...>(mFormatString, args...);
}
};
Here's a demo program:
Code:
#include <iostream>
#include "format.hpp"
struct IntNotify
{
int mData;
IntNotify(int data) : mData(data) { }
IntNotify(const IntNotify& other) = delete;
~IntNotify()
{
std::cout << Format("IntNotify destructor (%)\n")(mData);
}
};
std::ostream& operator<<(std::ostream& os, const IntNotify& intNotify)
{
return os << intNotify.mData;
}
int main()
{
std::cout << Format("int: %, char: %\n")(3, 'c');
Format dollarFmt("$%");
std::cout << dollarFmt(187) << std::endl;
std::cout << Format("This is ok: %")(IntNotify(4)) << std::endl;
FormatOutput<const IntNotify&> evil = Format("But don't do this: %")(IntNotify(7));
std::cout << evil << std::endl; // undefined/possible segfault
int x = 0;
FormatOutput<const int&> good = Format("Feel free to do this: %")(x);
x = 1;
std::cout << good << std::endl;
return 0;
}
Which has output:
Code:
int: 3, char: c
$187
This is ok: 4
IntNotify destructor (4)
IntNotify destructor (7)
But don't do this: 7
Feel free to do this: 1
What I'd like is some feedback about whether you would see this as useful, and comments on my use of C++11. I'm also interested in thoughts on the use of const char* here instead of string. The way I figure it, if I use std::string, it'll have to be significantly less efficient due to the extra traverse/copy of the string (although perhaps not practically noticeable).