I've written the following code to produce a formatted string (similar, but not identical to the formatting style that System.String.Format() does in .Net), using a combination of boost and the new variadic templates that C++11 offers. I'm looking for criticism, advice, and suggestions for improvement.
Please note: much of the code for handling type-specific formatting is not done. please do not comment on this, unless you have suggestions for its implementation.
Code:
#include <sstream>
#include <stdexcept>
#include <boost/lexical_cast.hpp>
template<typename T>
std::string BasicToString(const T& t)
{
return boost::lexical_cast<std::string>(t);
}
inline std::string Convert(const std::string& fmt, const int& i)
{
return BasicToString(i); // TODO: Add formatting capability
}
inline std::string Convert(const std::string& fmt, const long& l)
{
return BasicToString(l); // TODO: Add formatting capability
}
inline std::string Convert(const std::string& fmt, const long long& l)
{
return BasicToString(l); // TODO: Add formatting capability
}
inline std::string Convert(const std::string& fmt, const unsigned& u)
{
return BasicToString(u); // TODO: Add formatting capability
}
inline std::string Convert(const std::string& fmt, const unsigned long& ul)
{
return BasicToString(ul); // TODO: Add formatting capability
}
inline std::string Convert(const std::string& fmt, const unsigned long long& ul)
{
return BasicToString(ul); // TODO: Add formatting capability
}
inline std::string Convert(const std::string& fmt, const char& c)
{
return BasicToString(c); // TODO: Add formatting capability
}
inline std::string Convert(const std::string& fmt, const unsigned char& uc)
{
return BasicToString(uc); // TODO: Add formatting capability
}
inline std::string Convert(const std::string& fmt, const short& sh)
{
return BasicToString(sh); // TODO: Add formatting capability
}
inline std::string Convert(const std::string& fmt, const unsigned short& ush)
{
return BasicToString(ush); // TODO: Add formatting capability
}
inline std::string Convert(const std::string& fmt, const long double& d) // fmt contains the number of digits of precision after the decimal point or a 'C' for currency
{
std::stringstream ss;
if (fmt.empty())
{
return BasicToString(d);
}
else if (fmt[0] == 'C') // process it as currency
{
ss << "$"; // TODO: get the actual localized currency symbol
ss.precision(2); // TODO: Get the actual localized number of digits to print after the decimal point
ss << std::fixed << d;
return ss.str();
}
else
{
int len = boost::lexical_cast<int>(fmt);
ss.precision(len);
ss << std::fixed << d;
return ss.str();
}
}
inline std::string Convert(const std::string& fmt, const double& d) // fmt contains the number of digits of precision after the decimal point
{
std::stringstream ss;
if (fmt.empty())
{
return BasicToString(d);
}
else if (fmt[0] == 'C') // process it as currency
{
ss << "$"; // TODO: get the actual localized currency symbol
ss.precision(2); // TODO: Get the actual localized number of digits to print after the decimal point
ss << std::fixed << d;
return ss.str();
}
else
{
int len = boost::lexical_cast<int>(fmt);
ss.precision(len);
ss << std::fixed << d;
return ss.str();
}
}
inline std::string Convert(const std::string& fmt, const float& f) // fmt contains the number of digits of precision after the decimal point
{
std::stringstream ss;
if (fmt.empty())
{
return BasicToString(f);
}
else if (fmt[0] == 'C') // process it as currency
{
ss << "$"; // TODO: get the actual localized currency symbol
ss.precision(2); // TODO: Get the actual localized number of digits to print after the decimal point
ss << std::fixed << f;
return ss.str();
}
else
{
int len = boost::lexical_cast<int>(fmt);
ss.precision(len);
ss << std::fixed << f;
return ss.str();
}
}
inline std::string Convert(const std::string& fmt, const std::string& s) // fmt contains the minimum width to display, right aligned, padded with spaces
{
if (fmt.empty())
return BasicToString(s);
size_t len = 0;
try
{
len = boost::lexical_cast<int>(fmt);
}
catch (boost::bad_lexical_cast& ex)
{
throw std::invalid_argument("Invalid format string. The format string must contain only digits.");
}
if (s.length() <= len)
{
std::stringstream ss;
ss.width(len);
ss.fill(' ');
ss << s;
return ss.str();
}
else
return s;
}
inline std::string Convert(const std::string& fmt, const char* cp)
{
std::string s(cp);
return Convert(fmt, s);
}
template<typename T>
std::string GetArgByIndex(const std::string& argFmt, int index, T& t)
{
if (index)
throw std::invalid_argument("The argument index was out of range.");
else
return Convert(argFmt, t);
}
template<typename T, typename ... Args>
std::string GetArgByIndex(const std::string& argFmt, int index, T& t, Args ... args)
{
if (index)
return GetArgByIndex(argFmt, index - 1, args...);
else
return Convert(argFmt, t);
}
template<typename ... Args>
std::string StrFormat(const std::string& fmt, Args ... args)
{
std::string outString;
for (auto it = fmt.begin(); it != fmt.end(); ++it)
{
if (*it == '{')
{
// it's possibly the start of an argument
++it;
std::string argNum, argFmt;
if (*it == '{')
// not an argument, just append the literal '{' character
outString += *it;
else
{
// process it as an argument string
bool isFmtStr = false;
while (*it != '}')
{
switch (*it)
{
case ':': // the colon indicates the beginning of the format string for this argument
{
if (isFmtStr)
argFmt += *it; // include the colon in the format string. it may be used at a later date
else
isFmtStr = true;
break;
}
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
{
if (isFmtStr)
argFmt += *it;
else
argNum += *it;
break;
}
default:
{
if (isFmtStr)
argFmt += *it;
else
throw std::invalid_argument("Argument index must be an integer.");
break;
}
}
++it;
}
size_t index = 0;
if (argNum.empty())
throw std::invalid_argument("Empty argument index.");
try
{
index = boost::lexical_cast<size_t>(argNum);
}
catch (boost::bad_lexical_cast& ex)
{
throw std::invalid_argument("Argument index must be an integer.");
}
outString += GetArgByIndex(argFmt, index, args...);
}
}
else
// not an argument, just append the literal character
outString += *it;
}
return outString;
}
inline std::string StrFormat(const std::string& fmt = "")
{
return fmt;
}