这是一个使用C#格式化字符串风格来格式化C++字符串的库,它是类型安全的、多线程安全、可乱序、高效的的格式化库。
This is a library that uses the C# formatted string style to format C++ strings. It is a type-efficient, multi-thread-safe, reorderable formatting library.
本项目支持几乎常见所有主流编译,无论有无C++ 11支持都可以。我使用Visual Studio 2008/2010/2022、CodeBlock with gcc、xcode 15对项目进行了测试,并通过了测试。
This project supports almost all common mainstream compilations, whether with or without C++11 support. I tested the project using Visual Studio 2008/2010/2022, CodeBlock with gcc, xcode 15 and it passed.
这是一个纯C++头文件项目,clone项目,然后将项目根目录添加到头文件搜索路径中并包含Format/Format.hpp
即可。如果你是要配合C++标准库使用,那么直接包含头文件Format/StandardLibraryAdapter.hpp
也是可以的。
This is a pure C++ header file project, clone the project, then add the project root directory to the header file search path and include Format/Format.hpp
.If you want to use it with the C++ standard library, you can also directly include the header file Format/StandardLibraryAdapter.hpp
.
请先Clone本项目,然后使用CMake生成项目即可测试。如果你安装CMake时将其添加到了命令行,在Windows下还可以直接使用generate_projects.bat
来生成项目。
需要注意的一点是本项目依赖于googletests,所以你在clone之后,需要使用如下指令确保googletests的submodule正确被拉取下来了,否则CMake生成项目时会出错。
git submodule init
git submodule update
Please clone this project first, and then use CMake to generate the project for testing. If you added it to the command line when installing CMake, you can also use generate_projects.bat
directly to generate projects under Windows.
One thing to note is that this project depends on googletests, so after cloning, you need to use the following instructions to ensure that the submodule of googletests is correctly pulled down, otherwise CMake will make an error when generating the project.
另外如果你想要测试的C++编译器版本很早,比如不支持C++ 11的Visual Studio 2008
,那么你可能需要手动将googletests切换到更早的tag上,比如1.8.x
。
In addition, if the C++ compiler version you want to test is very early, such as Visual Studio 2008 that does not support C++ 11, then you may need to manually switch googletests to an earlier tag, such as 1.8.x
.
生成项目后,使用IDE打开生成的项目文件即可,编译和运行UniTests
项目即可开始测试。UnitTests_Format.cpp
中代码即是针对std::basic_string<TCharType>
的使用案例。你可以参考StandardLibraryAdapter.hpp的代码为你自己的字符串类添加适配器,即可让这套系统配合你的字符串类进行工作。
After generating the project, just use the IDE to open the generated project file, compile and run the UniTests
project to start testing. The code in UnitTests_Format.cpp
is a use case for std::basic_string<TCharType>
. You can refer to the code of StandardLibraryAdapter.hpp to add an adapter to your own string class, so that this system can work with your string class.
这里简单展示一下代码风格和用法,你可以在UnitTests_Format.cpp
中找到更多用法和案例。
Here is a brief demonstration of the code style and usage. You can find more usage and cases in UnitTests_Format.cpp
.
#include <Format/Format.hpp>
#include <Format/StandardLibraryAdapter.hpp>
using namespace Formatting;
TEST(Format, STL_Char_Format)
{
const std::string i0 = "Hello CppMiniToolkit";
std::string r0 = StandardLibrary::Format(i0.c_str());
EXPECT_EQ(r0, i0);
const std::string i1 = "Hello CppMiniToolkit {0}";
std::string r1 = StandardLibrary::Format(i1.c_str(), 1024);
EXPECT_EQ(r1, "Hello CppMiniToolkit 1024");
const std::string r2 = StandardLibrary::Format("{0}--#--{1,8}--#--{2}", 100, -40.2f, " String ");
EXPECT_EQ(r2, "100--#-- -40.20--#-- String ");
const std::string r3 = StandardLibrary::Format("{0}--#--{1,8}--#--{1}", 100, -40.2f);
EXPECT_EQ(r3, "100--#-- -40.20--#---40.20");
const std::string r4 = StandardLibrary::Format("{0}--#--{1,8}--#--{3}", 100, -40.2f, std::string("xxx"));
EXPECT_EQ(r4, "100--#-- -40.20--#--{3}");
const std::string r5 = StandardLibrary::Format("{0}", char('c'), short(2));
EXPECT_EQ(r5, "c");
const std::string r6 = StandardLibrary::Format("0x{0:x}", 100, (unsigned long)(100));
EXPECT_EQ(r6, "0x64");
// gen compile error
//StandardLibrary::Format("{0}", std::wstring(L"x"));
}
TEST(Format, STL_WChar_Format)
{
const std::wstring r7 = StandardLibrary::Format(L"Test{1}, {2:f4}, {0}, {0,4}", L" X ", 20, -10.005f);
EXPECT_EQ(r7, L"Test20, -10.0050, X , X ");
const std::wstring r8 = StandardLibrary::Format(L"Test{1}, {2:f4}, {0}, {0,4}");
EXPECT_EQ(r8, L"Test{1}, {2:f4}, {0}, {0,4}");
const std::wstring r9 = StandardLibrary::Format(std::wstring(L"Test{1}, {2:f4}, {0}, {0,4}"), L" X ", 20, -10.005f);
EXPECT_EQ(r9, L"Test20, -10.0050, X , X ");
const std::wstring r11 = StandardLibrary::Format(L"\u4F60\u597D : {0}", L"\u4E2D\u6587");
EXPECT_EQ(r11, L"\u4F60\u597D : \u4E2D\u6587");
}
TEST(Format, STL_WChar_FormatTo)
{
std::wstring v;
StandardLibrary::FormatTo(v, L"Test{1}, {2:f4}, {0}, {0,4}", L" X ", 20, -10.005f);
EXPECT_EQ(v, L"Test20, -10.0050, X , X ");
// test invalid param
StandardLibrary::FormatTo(v, L"Test{1}, {2:f4}, {0}, {0,4}");
EXPECT_EQ(v, L"Test{1}, {2:f4}, {0}, {0,4}");
StandardLibrary::FormatTo(v, std::wstring(L"Test{1}, {2:f4}, {0}, {0,4}"), L" X ", 20, -10.005f);
EXPECT_EQ(v, L"Test20, -10.0050, X , X ");
StandardLibrary::FormatTo(v, L"\u4F60\u597D : {0}", L"\u4E2D\u6587");
EXPECT_EQ(v, L"\u4F60\u597D : \u4E2D\u6587");
}
项目为C++ 11做了针对性优化,如果你开启了C++ 11支持,那么你可以考虑使用下面的宏,这样将获得更快的执行速度:
The project has been specifically optimized for C++ 11. If you enable C++ 11 support, you may consider using the following macros, which will result in faster execution:
#ifndef FL_DISABLE_STANDARD_LIBARY_MACROS
TEST(Format, STL_Char_Format_FL_STD_FORMAT)
{
const std::string r2 = FL_STD_FORMAT("{0}--#--{1,8}--#--{2}", 100, -40.2f, " String ");
EXPECT_EQ(r2, "100--#-- -40.20--#-- String ");
const std::string r3 = FL_STD_FORMAT("{0}--#--{1,8}--#--{1}", 100, -40.2f);
EXPECT_EQ(r3, "100--#-- -40.20--#---40.20");
const std::string r4 = FL_STD_FORMAT("{0}--#--{1,8}--#--{3}", 100, -40.2f, std::string("xxx"));
EXPECT_EQ(r4, "100--#-- -40.20--#--{3}");
const std::string r5 = FL_STD_FORMAT("{0}", char('c'), short(2));
EXPECT_EQ(r5, "c");
const std::string r6 = FL_STD_FORMAT("0x{0:x}", 100, (unsigned long)(100));
EXPECT_EQ(r6, "0x64");
// can't compile
// FL_CONSTEXPR20 const char* fmt = "0x{0:x}";
// const std::string r7 = FL_STD_FORMAT(fmt, 100, (unsigned long)(100));
// EXPECT_EQ(r6, "0x64");
// can't compile
// const char* fmt = "0x{0:x}";
// const std::string r7 = FL_STD_FORMAT(fmt, 100, (unsigned long)(100));
// EXPECT_EQ(r6, "0x64");
}
#endif
注意:使用宏时格式化字符串只能是字符串常量,无论是constexpr表达式还是static const char*
等都不被支持。这是因为该宏会在原地创建一个格式化字符串的静态TFormatPattern,只有使用这种方式才能保证TFormatPattern不会被错误的应用到错误的参数上。
Note: When using macros, the formatted string can only be string constants. Neither constexpr expressions nor static const char*
are supported. This is because this macro will create a static TFormatPattern of the formatted string in place. Only by using this method can we ensure that TFormatPattern will not be mistakenly applied to the wrong parameters.
想要将格式化库适配你自己的字符串类或者使用你自己的容器类来接管格式化库内部的容器,那么你只需要做三件事:
- 第一,实现自己的Policy类,这个类需要告知框架必须要的基础类型都是什么,这通过typedef来实现;并且你还需要实现几个基础接口即可:
FindByHashKey
、ReserveList
、AppendPattern
。 - 第二,你需要在命名空间
Formatting::Shims
下实现两个垫片函数PtrOf
和LengthOf
,这两个函数是针对你的字符串类的重载。 - 第三,你需要实现自己的
Format
函数。如果你使用C++ 11或更新标准,那么你只需要使用不定参数模板即可;如果你要支持C++ 11更早的版本,你需要用到一些宏技巧来生成支持多个参数的Format
函数。
你可以参考UnitTests/Sources/MFCAdapter.hpp
的源代码,这里展示了如何为MFC字符串提供Format支持。当然也可以参考Format/StandardLibraryAdapter.hpp
,这里是针对stl::basic_string<TCharType>
的适配代码。
通过这几个简单的适配器,你就可以将这个字符串格式化库用到你自己的类型上了。
If you want to adapt the formatting library to your own string class or use your own container class to take over the container inside the formatting library, then you only need to do three things:
- First, implement your own Policy class. This class needs to tell the framework what the basic types are. This is achieved through typedef; and you also need to implement several basic interfaces:
FindByHashKey
,ReserveList
,AppendPattern
. - Second, you need to implement two shim functions
PtrOf
andLengthOf
under the namespaceFormatting::Shims
. These two functions are overloaded for your string class. - Third, you need to implement your own
Format
function. If you use C++11 or newer standards, then you only need to use indefinite parameter templates; if you want to support earlier versions of C++11, you need to use some macro tricks to generate aFormat
function that supports multiple parameters.
You can refer to the source code ofUnitTests/Sources/MFCAdapter.hpp
, which shows how to provide Format support for MFC strings. Of course, you can also refer toFormat/StandardLibraryAdapter.hpp
, here is the adaptation code forstl::basic_string<TCharType>
.
With these simple adapters, you can apply this string formatting library to your own types.
CPPStringFormatting本身是类型安全的系统,如果你尝试将不支持的类型提交给格式化函数,那么你将会在编译阶段得到一个编译错误。要解决这个错误,你有两种办法:
- 第一,将参数转换成受支持的类型。
- 第二,提供一个针对该类型的自定义Translator,这样框架将能自动帮你完成转换操作。
你可以在
UnitTests/Sources/UnityTests_Extends.cpp
中找到扩展的方法和举例。
CPPStringFormatting itself is a type-safe system, if you try to submit an unsupported type to the formatting function, then you will get a compilation error during the compilation phase. To resolve this error, you have two options:
- First, convert the parameters to a supported type.
- Second, provide a custom Translator for this type, so that the framework will automatically complete the conversion operation for you.
You can find extension methods and examples in
UnitTests/Sources/UnityTests_Extends.cpp
.
// The following class provides automatic conversion capabilities for Vector3, so that Vector3 type parameters can be formatted directly.
namespace Formatting
{
namespace Details
{
// convert Vector3 to string
template <>
class TTranslator< char, Vector3 > :
public TTranslatorBase< char, Vector3 >
{
public:
typedef TTranslatorBase< char, Vector3 > Super;
typedef Super::CharType CharType;
typedef Super::FormatPattern FormatPattern;
typedef Super::ByteType ByteType;
typedef Super::SizeType SizeType;
typedef Super::StringType StringType;
typedef Super::CharTraits CharTraits;
static bool Transfer(StringType& strRef, const FormatPattern& pattern, const Vector3& arg)
{
std::string text = arg.ToString();
Super::AppendString(strRef, pattern, text.c_str(), text.size());
return true;
}
};
// convert Vector3 to wstring
template <>
class TTranslator< wchar_t, Vector3 > :
public TTranslatorBase< wchar_t, Vector3 >
{
public:
typedef TTranslatorBase< wchar_t, Vector3 > Super;
typedef Super::CharType CharType;
typedef Super::FormatPattern FormatPattern;
typedef Super::ByteType ByteType;
typedef Super::SizeType SizeType;
typedef Super::StringType StringType;
typedef Super::CharTraits CharTraits;
static bool Transfer(StringType& strRef, const FormatPattern& pattern, const Vector3& arg)
{
std::wstring text = arg.ToWString();
Super::AppendString(strRef, pattern, text.c_str(), text.size());
return true;
}
};
}
}
虽然CPPStringFormatting支持了大部分常见的格式化需求,但并也有许多C#格式化的功能尚不支持。具体C#支持的格式化规范请参考这里: https://learn.microsoft.com/en-us/dotnet/standard/base-types/standard-numeric-format-strings
Although CPPStringFormatting supports most common formatting needs, there are also many C# formatting functions that are not yet supported. For specific formatting specifications supported by C#, please refer here:
https://learn.microsoft.com/en-us/dotnet/standard/base-types/standard-numeric-format-strings
Type | Status |
---|---|
B | ✔ |
C | ✖ |
D | ✔ |
E | ✔ |
F | ✔ |
G | ✖ |
N | ✖ |
P | ✖ |
R | ✖ |
X | ✔ |
Percision | ✔ |
Width | ✔ |
遇到不支持的格式标志符时C#会抛出异常,CPPStringFormatting会直接输出格式化字符串,一些情况会触发assert或忽略。因此当你遇到与预期不一致的问题时,请检查格式标志符是否正确,或者是否使用了不被支持的标志符。
C# will throw an exception when encountering an unsupported format identifier, and CPPStringFormatting will directly output the format string. In some cases, assert or ignore will be triggered. So when you encounter problems that are inconsistent with expectations, please check whether the format identifier is correct, or whether an unsupported identifier is used.
使用CMake生成了项目后,你可以启动Benchmark项目来进行性能测试。性能测试基于Celero,所以目前这个项目无法支持C++ 11之前的版本了。不同的C++标准版本的性能是有差异的,一般来说C++版本越新,性能会更高。
After using CMake to generate the project, you can start the Benchmark project for performance testing. The performance test is based on Celero, so this project currently cannot support versions before C++ 11. The performance of different C++ standard versions is different. Generally speaking, the newer the C++ version, the higher the performance will be.
直接通过Issues页面提交即可
Submit directly through the Issues page