C++ - Summary of std::string string formatting methods

1 C++ std::string string formatting

In Python, we can conveniently format strings using the following code

if __name__ == '__main__':
    format_str = "There are {} fools in the world".format(10)
    print(format_str)

Not only Python, strings can also be formatted well in other high-level languages.

This article will summarize string formatting methods in C++, including:

  • How to format string in C language
  • How to format strings in versions before C++20
  • C++20 string formatting standard library function std::format

1.1 String formatting in C language

In C language we can use

int sprintf(char* buffer, const char* format, ... );     //不推荐使用 
int snprintf(char* buffer, std::size_t buf_size, const char* format, ... );

Do string formatting, e.g.

#include <iostream>

int main()
{
    
    
	char format_str[64] = {
    
     0 };
	snprintf(format_str, sizeof(format_str) - 1, "There are %d fools in the world", 10);
	std::cout << format_str << std::endl;
}

1.2 C++ uses std::stringstream for string formatting

In C++, the C++ standard library did not provide a standard string formatting function for the std::string string class before C++20. We can only piece together strings by using string streams, such std::stringstreamas

#include <iostream>
#include <sstream>

int main()
{
    
    
	std::stringstream ss;
	ss << "There are ";
	ss << 10;
	ss << " fools in the world";

	std::cout << ss.str() << std::endl;

	return 0;
}

This kind of code is really smelly and long, especially when there are many formatting parameters.

1.3 Open source C++ single header file string formatting tool

This section introduces simple and easy-to-use cross-platform open source tools for string formatting before C++20.

1.3.1 format

Github address: https://github.com/arajar/format

This is a C++11 standard std::string string formatting tool with only a single header file. It has only one Format.hfile. The header file code is as follows

#pragma once

#include <string>
#include <vector>
#include <stdlib.h>
#include <iostream>
#include <algorithm>
#include <sstream>
#include <iomanip>

namespace util
{
    
    
	class ArgBase
	{
    
    
	public:
		ArgBase() {
    
    }
		virtual ~ArgBase() {
    
    }
		virtual void Format(std::ostringstream &ss, const std::string& fmt) = 0;
	};

	template <class T>
	class Arg : public ArgBase
	{
    
    
	public:
		Arg(T arg) : m_arg(arg) {
    
    }
		virtual ~Arg(){
    
    }
		virtual void Format(std::ostringstream &ss, const std::string& fmt)
		{
    
    
			ss << m_arg;
		}
	private:
		T m_arg;
	};

	class ArgArray : public std::vector < ArgBase* >
	{
    
    
	public:
		ArgArray() {
    
    }
		~ArgArray()
		{
    
    
			std::for_each(begin(), end(), [](ArgBase* p){
    
     delete p; });
		}
	};

	static void FormatItem(std::ostringstream& ss, const std::string& item, const ArgArray& args)
	{
    
    
		int index = 0;
		int alignment = 0;
		std::string fmt;

		char* endptr = nullptr;
		index = strtol(&item[0], &endptr, 10);
		if (index < 0 || index >= args.size())
		{
    
    
			return;
		}

		if (*endptr == ',')
		{
    
    
			alignment = strtol(endptr + 1, &endptr, 10);
			if (alignment > 0)
			{
    
    
				ss << std::right << std::setw(alignment);
			}
			else if (alignment < 0)
			{
    
    
				ss << std::left << std::setw(-alignment);
			}
		}

		if (*endptr == ':')
		{
    
    
			fmt = endptr + 1;
		}

		args[index]->Format(ss, fmt);

		return;
	}

	template <class T>
	static void Transfer(ArgArray& argArray, T t)
	{
    
    
		argArray.push_back(new Arg<T>(t));
	}

	template <class T, typename... Args>
	static void Transfer(ArgArray& argArray, T t, Args&&... args)
	{
    
    
		Transfer(argArray, t);
		Transfer(argArray, args...);
	}

	template <typename... Args>
	std::string Format(const std::string& format, Args&&... args)
	{
    
    
		if (sizeof...(args) == 0)
		{
    
    
			return format;
		}

		ArgArray argArray;
		Transfer(argArray, args...);
		size_t start = 0;
		size_t pos = 0;
		std::ostringstream ss;
		while (true)
		{
    
    
			pos = format.find('{', start);
			if (pos == std::string::npos)
			{
    
    
				ss << format.substr(start);
				break;
			}

			ss << format.substr(start, pos - start);
			if (format[pos + 1] == '{')
			{
    
    
				ss << '{';
				start = pos + 2;
				continue;
			}

			start = pos + 1;
			pos = format.find('}', start);
			if (pos == std::string::npos)
			{
    
    
				ss << format.substr(start - 1);
				break;
			}

			FormatItem(ss, format.substr(start, pos - start), argArray);
			start = pos + 1;
		}

		return ss.str();
	}
}

Instructions

#include <iostream>
#include "Format.h"

int main()
{
    
    
	std::string format_str = util::Format("There are {0} fools in the world",10);
	std::cout << format_str << std::endl;

	return 0;
}

1.3.2 demoul

Github:https://github.com/mmc1993/sformat

This is also a string formatting tool with only a single header file. When using it, we only need to include the sformat.hheader file. The code content of the header file is as follows

#pragma once

#include <tuple>
#include <string>
#include <algorithm>

template <class T>
inline void ToString(std::string & ret, T && val)
{
    ret.append(std::to_string(std::forward<T>(val)));
}

inline void ToString(std::string & ret, const std::string & val)
{
    ret.append(val);
}

inline void ToString(std::string & ret, const char * val)
{
    ret.append(val);
}

template <int N>
struct SFormatN {
    static std::string Format(const char * fmt)
    {
        static_assert(false, "");
    }
};

template <>
struct SFormatN<0> {
    template <class ...ARGS>
    static std::string Format(const char * fmt, const std::tuple<ARGS...> &)
    {
        return fmt;
    }
};

template <class ...ARGS>
std::string SFormat(const char * fmt, const ARGS &...args)
{
    const auto tuple = std::forward_as_tuple(args...);
    return SFormatN<sizeof...(args)>::Format(fmt, tuple);
}

#define FMT_N(idx)	case idx: ToString(ret, std::get<idx>(args)); break;

#define FMT_PARSE(N, ...)															\
template <>																			\
struct SFormatN<N> {																\
	template <class... ARGS>														\
	static std::string Format(const char * fmt, const std::tuple<ARGS...> & args)	\
	{	std::string ret;															\
		while (*fmt != '\0') { auto idx = -1;										\
			if (*fmt == '{') { idx = 0; ++fmt;										\
				while (*fmt >= '0' && *fmt <= '9')									\
					{ idx *= 10; idx += (int)(*fmt++ - '0'); }						\
				if (*fmt != '}') idx = -1; else ++fmt;								\
			}																		\
			switch (idx) { __VA_ARGS__ default: ret.append(1, *fmt++); break; }		\
		}																			\
		return ret;																	\
	}																				\
};

FMT_PARSE(1, FMT_N(0))
FMT_PARSE(2, FMT_N(0) FMT_N(1))
FMT_PARSE(3, FMT_N(0) FMT_N(1) FMT_N(2))
FMT_PARSE(4, FMT_N(0) FMT_N(1) FMT_N(2) FMT_N(3))
FMT_PARSE(5, FMT_N(0) FMT_N(1) FMT_N(2) FMT_N(3) FMT_N(4))
FMT_PARSE(6, FMT_N(0) FMT_N(1) FMT_N(2) FMT_N(3) FMT_N(4) FMT_N(5))
FMT_PARSE(7, FMT_N(0) FMT_N(1) FMT_N(2) FMT_N(3) FMT_N(4) FMT_N(5) FMT_N(6))
FMT_PARSE(8, FMT_N(0) FMT_N(1) FMT_N(2) FMT_N(3) FMT_N(4) FMT_N(5) FMT_N(6) FMT_N(7))
FMT_PARSE(9, FMT_N(0) FMT_N(1) FMT_N(2) FMT_N(3) FMT_N(4) FMT_N(5) FMT_N(6) FMT_N(7) FMT_N(8))
FMT_PARSE(10, FMT_N(0) FMT_N(1) FMT_N(2) FMT_N(3) FMT_N(4) FMT_N(5) FMT_N(6) FMT_N(7) FMT_N(8) FMT_N(9))
FMT_PARSE(11, FMT_N(0) FMT_N(1) FMT_N(2) FMT_N(3) FMT_N(4) FMT_N(5) FMT_N(6) FMT_N(7) FMT_N(8) FMT_N(9) FMT_N(10))
FMT_PARSE(12, FMT_N(0) FMT_N(1) FMT_N(2) FMT_N(3) FMT_N(4) FMT_N(5) FMT_N(6) FMT_N(7) FMT_N(8) FMT_N(9) FMT_N(10) FMT_N(11))
FMT_PARSE(13, FMT_N(0) FMT_N(1) FMT_N(2) FMT_N(3) FMT_N(4) FMT_N(5) FMT_N(6) FMT_N(7) FMT_N(8) FMT_N(9) FMT_N(10) FMT_N(11) FMT_N(12))
FMT_PARSE(14, FMT_N(0) FMT_N(1) FMT_N(2) FMT_N(3) FMT_N(4) FMT_N(5) FMT_N(6) FMT_N(7) FMT_N(8) FMT_N(9) FMT_N(10) FMT_N(11) FMT_N(12) FMT_N(13))
FMT_PARSE(15, FMT_N(0) FMT_N(1) FMT_N(2) FMT_N(3) FMT_N(4) FMT_N(5) FMT_N(6) FMT_N(7) FMT_N(8) FMT_N(9) FMT_N(10) FMT_N(11) FMT_N(12) FMT_N(13) FMT_N(14))
FMT_PARSE(16, FMT_N(0) FMT_N(1) FMT_N(2) FMT_N(3) FMT_N(4) FMT_N(5) FMT_N(6) FMT_N(7) FMT_N(8) FMT_N(9) FMT_N(10) FMT_N(11) FMT_N(12) FMT_N(13) FMT_N(14) FMT_N(15))
FMT_PARSE(17, FMT_N(0) FMT_N(1) FMT_N(2) FMT_N(3) FMT_N(4) FMT_N(5) FMT_N(6) FMT_N(7) FMT_N(8) FMT_N(9) FMT_N(10) FMT_N(11) FMT_N(12) FMT_N(13) FMT_N(14) FMT_N(15) FMT_N(16))
FMT_PARSE(18, FMT_N(0) FMT_N(1) FMT_N(2) FMT_N(3) FMT_N(4) FMT_N(5) FMT_N(6) FMT_N(7) FMT_N(8) FMT_N(9) FMT_N(10) FMT_N(11) FMT_N(12) FMT_N(13) FMT_N(14) FMT_N(15) FMT_N(16) FMT_N(17))
FMT_PARSE(19, FMT_N(0) FMT_N(1) FMT_N(2) FMT_N(3) FMT_N(4) FMT_N(5) FMT_N(6) FMT_N(7) FMT_N(8) FMT_N(9) FMT_N(10) FMT_N(11) FMT_N(12) FMT_N(13) FMT_N(14) FMT_N(15) FMT_N(16) FMT_N(17) FMT_N(18))
FMT_PARSE(20, FMT_N(0) FMT_N(1) FMT_N(2) FMT_N(3) FMT_N(4) FMT_N(5) FMT_N(6) FMT_N(7) FMT_N(8) FMT_N(9) FMT_N(10) FMT_N(11) FMT_N(12) FMT_N(13) FMT_N(14) FMT_N(15) FMT_N(16) FMT_N(17) FMT_N(18) FMT_N(19))
FMT_PARSE(21, FMT_N(0) FMT_N(1) FMT_N(2) FMT_N(3) FMT_N(4) FMT_N(5) FMT_N(6) FMT_N(7) FMT_N(8) FMT_N(9) FMT_N(10) FMT_N(11) FMT_N(12) FMT_N(13) FMT_N(14) FMT_N(15) FMT_N(16) FMT_N(17) FMT_N(18) FMT_N(19) FMT_N(20))
FMT_PARSE(22, FMT_N(0) FMT_N(1) FMT_N(2) FMT_N(3) FMT_N(4) FMT_N(5) FMT_N(6) FMT_N(7) FMT_N(8) FMT_N(9) FMT_N(10) FMT_N(11) FMT_N(12) FMT_N(13) FMT_N(14) FMT_N(15) FMT_N(16) FMT_N(17) FMT_N(18) FMT_N(19) FMT_N(20) FMT_N(21))
FMT_PARSE(23, FMT_N(0) FMT_N(1) FMT_N(2) FMT_N(3) FMT_N(4) FMT_N(5) FMT_N(6) FMT_N(7) FMT_N(8) FMT_N(9) FMT_N(10) FMT_N(11) FMT_N(12) FMT_N(13) FMT_N(14) FMT_N(15) FMT_N(16) FMT_N(17) FMT_N(18) FMT_N(19) FMT_N(20) FMT_N(21) FMT_N(22))
FMT_PARSE(24, FMT_N(0) FMT_N(1) FMT_N(2) FMT_N(3) FMT_N(4) FMT_N(5) FMT_N(6) FMT_N(7) FMT_N(8) FMT_N(9) FMT_N(10) FMT_N(11) FMT_N(12) FMT_N(13) FMT_N(14) FMT_N(15) FMT_N(16) FMT_N(17) FMT_N(18) FMT_N(19) FMT_N(20) FMT_N(21) FMT_N(22) FMT_N(23))

From the above code, I personally think that the implemented code is not as concise as the format in section 1.1.1, and the maximum supported format parameters are 24, which is also the defect of this tool.

Instructions

#include <iostream>
#include "sformat.h"

int main()
{
    
    
	std::string format_str = SFormat("There are {0} fools in the world",10);
	std::cout << format_str << std::endl;

	return 0;
}

1.4 Customized C++ string formatting function

We can use variadic templates + std::snprintfdefine a string formatting function

// std::string的字符串格式化函数
template<typename ... Args>
static std::string str_format(const std::string &format, Args ... args)
{
    
    
	auto size_buf = std::snprintf(nullptr, 0, format.c_str(), args ...) + 1; 
	std::unique_ptr<char[]> buf(new(std::nothrow) char[size_buf]);

	if (!buf)
		return std::string("");

	std::snprintf(buf.get(), size_buf, format.c_str(), args ...);
	return std::string(buf.get(), buf.get() + size_buf - 1); 
}

// std::wstring的字符串格式化函数
template<typename ... Args>
static std::wstring wstr_format(const std::wstring &format, Args ... args)
{
    
    
	auto size_buf = std::snprintf(nullptr, 0, format.c_str(), args ...) + 1; 
	std::unique_ptr<char[]> buf(new(std::nothrow) char[size_buf]);

	if (!buf)
		return std::wstring("");

	std::snprintf(buf.get(), size_buf, format.c_str(), args ...);
	return std::wstring(buf.get(), buf.get() + size_buf - 1); 
}

Instructions

#include <iostream>

template<typename ... Args>
static std::string str_format(const std::string& format, Args ... args)
{
    
    
	auto size_buf = std::snprintf(nullptr, 0, format.c_str(), args ...) + 1;
	std::unique_ptr<char[]> buf(new(std::nothrow) char[size_buf]);

	if (!buf)
		return std::string("");

	std::snprintf(buf.get(), size_buf, format.c_str(), args ...);
	return std::string(buf.get(), buf.get() + size_buf - 1);
}

int main()
{
    
    
	std::string format_str = str_format("There are %d fools in the world", 10);

	std::cout << format_str << std::endl;

	return 0;
}

1.5 C++20 string standard library function std::format

Unable to withstand the complaints of the masses, the C++20 standard finally introduced the string formatting function of the standard library std::format, which supports both std::stringwide character std::wstringformatting.

function prototype

template<class... Args>
std::string format(std::string_view fmt, const Args&... args);

template<class... Args>
std::wstring format(std::wstring_view fmt, const Args&... args);

template<class... Args>
std::string format(const std::locale& loc, std::string_view fmt, const Args&... args);

template<class... Args>
std::wstring format(const std::locale& loc, std::wstring_view fmt, const Args&... args);

Function usage

#include <iostream>
#include <format>

int main()
{
    
    
    std::string format_str = std::format("There are {} fools in the world",10);
    std::cout << format_str << std::endl;

	return 0;
}

The above code can only be compiled and passed on a compiler that supports the C++20 standard, but I have to lament that C++ has suffered from string formatting for a long time! ! !

Welcome to my personal website: https://www.stubbornhuang.com/

Guess you like

Origin blog.csdn.net/HW140701/article/details/127897839