#include <string>
//Overload or specialize for custom types.
template<typename Type>
std::string ToString(const Type &type)
{
return std::to_string(type);
}
//Dummy 'conversion' for arguments that are already strings.
std::string ToString(const std::string &str) { return str; }
std::string ToString(const char *str) { return std::string(str); }
std::string ToString(char c) { return std::string(1, c); }
//====================================
//Counts (at compile-time) the number of 'decimal places' in an integer (e.g. 17 = 2 symbols, 555 = 3 symbols, 12345 = 5 symbols, 0 = 1 symbol).
template<unsigned int base = 10, unsigned int result = 1>
constexpr unsigned int NumSymbolsAsString(unsigned int value)
{
static_assert(base >= 2, "'base' must be 2 or greater.");
static_assert(result <= 32, "Whatever base you are using, you'll need to add an ending function for it, or it'll compile infinitely.");
return ((value < base)? result : NumSymbolsAsString<base, result+1>(value/base));
}
//Set a limit on how many functions to generate at compile-time, or it'd compile infinitely.
//A max of 32 for binary, but 10 symbols max for base-10 (4294967295 = 10 chars), and only 8 for base-16 (FFFF-FFFF = 8 symbols).
template<> constexpr unsigned int NumSymbolsAsString<2, 32>(unsigned int value) { return 32; }
template<> constexpr unsigned int NumSymbolsAsString<8, 11>(unsigned int value) { return 11; }
template<> constexpr unsigned int NumSymbolsAsString<10, 10>(unsigned int value) { return 10; }
template<> constexpr unsigned int NumSymbolsAsString<16, 8>(unsigned int value) { return 8; }
//====================================
//Ends the variadic template.
template<unsigned int argIndex>
void priv_Format(std::string &input, char symbol, char escapeSymbol)
{
return;
}
template<unsigned int argIndex, typename Arg, typename... Args>
void priv_Format(std::string &input, char symbol, char escapeSymbol, const Arg &arg, Args &&... args)
{
//Check how many chars are needed to represent the number 'argIndex'
unsigned int indexCharSize = NumSymbolsAsString(argIndex);
//Convert the argument to a string.
std::string argumentAsText = ToString(arg);
//Iterate over the input string.
char prevChar = '\0';
for(size_t i = 0; i < (input.size() - indexCharSize); ++i)
{
//Check for the character 'symbol', as long as it's not escaped.
if(input[i] == symbol && prevChar != escapeSymbol)
{
//Make sure this symbol's number is the same index as our argument.
if(std::stoi(std::string(input, i+1, indexCharSize)) == argIndex)
{
//We're replacing the symbol and the number after it.
size_t replacementSize = (1 + indexCharSize);
//Replace the symbol with the argument.
input.replace(i, replacementSize, argumentAsText);
//Adjust our iterator to compensate for the removal + insertion.
i -= replacementSize;
i += argumentAsText.size();
}
}
prevChar = input[i];
}
//Go on to the next argument.
priv_Format<argIndex+1>(input, symbol, escapeSymbol, std::forward<Args>(args)...);
}
//Iterates over 'input' replacing every occurance of '%n' with the nth argument.
//
//Speed concerns for the next time I'm bored:
// Iterates over the entire string once per argument.
// Calls std::string::replace (potentially reallocating the string) once for every symbol
// in 'input' that has a matching argument (e.g. once for every %n if an argument actually exists for 'n').
template<typename... Args>
std::string Format(const std::string &input, Args &&... args)
{
std::string output(input);
priv_Format<1>(output, '%', '\\', std::forward<Args>(args)...);
return output;
}
//=============================
#include <iostream>
struct CustomType
{
int x;
int y;
};
std::string ToString(const CustomType &type)
{
return std::string("(") + ToString(type.x) + ", " + ToString(type.y) + ")";
}
int main(void)
{
std::cout << Format("Test") << std::endl;
std::cout << Format("Today is %2/%1/%3", 9, 4, 2014) << std::endl; //Arguments don't have to be in order (useful for localization).
std::cout << Format("Today is %2 %1, %3", std::string("9th"), "April", 2014) << std::endl; //Mix and match argument types.
std::cout << Format("%1 %2 %3 %4 %3 %2 %1", 'W', 'X', 'Y', 'Z') << std::endl; //Arguments can be repeated in the string.
std::cout << Format("Position: %2 at a distance of %1", 4.057f, CustomType{357,100}) << std::endl; //Support for custom types by overloading ToString().
return 0;
}