[C++] Is there an easy way to mimic printf()'s behavior?

Started by
22 comments, last by Servant of the Lord 10 years ago

Wow! Thanks to everyone who replied. Apparently there's a lot of ways to go about this. I'll be trying out some of your methods and see which one fits my project better. Thanks again. :)

Advertisement
Being a "professional" is completely orthogonal to being right.

It's true, a lot of person is professional and doesn't know what they really do or the math behind what they do.

It's very the truth when this is person who is just out of school, they have no experience and it's a problem.

I don't say boost is bad, I just say it has to be used in a right way since it's a heavy weight lib.

If it's just for one module, it's too heavy to use boost, alternative exist.


If it's just for one module, it's too heavy to use boost,

Start a new thread called "Blanket statement: boost::format is too heavyweight to use. Discuss."

Had a few hours to kill, so I wrote this function:


//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;
}


Which given this:


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;
}

Outputs this:
Test

Today is 4/9/2014
Today is April 9th, 2014
W X Y Z Y X W
Position: (357, 100) at a distance of 4.057000

[Ideone example]

For additional formatting of arguments, you could format them when you pass them in:


//Converts an integer to a std::string, in hexadecimal format. 'digits' is the fixed number of digits that show up.
//For example, 0x00FF00AA shows zeroes if 'digits' is 8, and only shows 0x00AA, if 'digits' is 4.
std::string UnsignedIntToHexString(uint32_t value, const std::string &prefix = "0x", size_t digits = 8, bool uppercase = true);

//Apparently I don't (yet) have a float-to-string conversion function that lets you fine-tune the precision.

int main()
{
    char *errorMessage = ...;
    float size = ...;
    unsigned int address = ...;

    String::Format("Error: %1 at %3 with parameter %2 and size %4", errorMessage, 54, UnsignedIntToHexString(address, "0x", 8), size);
}

But it would be nice (and helpful for localization) to be able to directly pass into the input string additional formatting information... I'm thinking that ToString() could be expanded to handle that, and could be passed in additional data like "This text %1[.2F] has formatted data", with the text in the square brackets (".2F") being passed in to ToString() as a second argument, or something like that.

In other cases, the custom types passed in should maybe change their formatting based on the locale. Like dates and times, to have different spellings for different languages ("January" -> "Enero" (spanish)), and different layouts (day/month/year, month/day/year, etc...) - the overloads of ToString() for custom types could handle that (calling String::Format() for the date orderings, or looking up in a map the month names).

This topic is closed to new replies.

Advertisement