String.Format for C ++

Looking for an implementation for C ++ of a function like the .NET String.Format. Obviously there is printf and its variations, but I'm looking for something positional, as in:

String.Format ("Hello there {0}. {1} years. How are you feeling {1}?", Name, age);

This is necessary because we will try to simplify the localization of our application and give translators {0} and {1} a position anywhere in the sentence is much simpler than giving them% s,% d,% d, which should be placed in this order in their translation.

I assume that finding and replacing with variable inputs (va_start, va_end, etc.) is what I will eventually build, but if there is already a solid solution, that would be preferable.

Thank:)

+26
c ++ printf string.format localization
Jan 20 '09 at 18:52
source share
13 answers

Many good recommendations above that will work in most situations. In my case, I ended up wanting to load strings from a resource, and keep the string resources as close to the .NET String.Format as I could, so I made my own. Having considered some of the above implementations for ideas, the resulting implementation was quite short and easy.

There is a String class, which in my case comes from Microsoft CString, but it can be obtained from any string class. There is also the StringArg class - this task is to take any type of parameter and turn it into a string (i.e. It mimics ToString in .NET). If the new object should be ToString'd, you simply add another constructor. The constructor allows the printf format specifier for default formatting.

Then the String class takes the identifier of the string table for the source string, the number of StringArg parameters and finally the optional HINSTANCE (I use a large number of DLLs, any of which can contain the string table, so this allowed me to go through this or use the default DLL-specific HINSTANCE )

Examples of using:

dlg.m_prompt = String(1417); //"Welcome to Stackoverflow!" MessageBox(String(1532, m_username)); //"Hi {0}" 

Be that as it may, only a line identifier is required for input, but it would be trivial to add an input line instead of a line identifier:

 CString s = String.Format("Hi {0}, you are {1} years old in Hexidecimal", m_userName, StringArg(m_age, "%0X")); 

Now for the StringArg class, which executes the ToString equivalent for variables:

 class StringArg { StringArg(); //not implemented StringArg(const StringArg&); //not implemented StringArg& operator=(const StringArg&); //not implemented public: StringArg(LPCWSTR val); StringArg(const CString& val); StringArg(int val, LPCWSTR formatSpec = NULL); StringArg(size_t val, LPCWSTR formatSpec = NULL); StringArg(WORD val, LPCWSTR formatSpec = NULL); StringArg(DWORD val, LPCWSTR formatSpec = NULL); StringArg(__int64 val, LPCWSTR formatSpec = NULL); StringArg(double val, LPCWSTR formatSpec = NULL); CString ToString() const; private: CString m_strVal; }; extern HINSTANCE GetModuleHInst(); //every DLL implements this for getting it own HINSTANCE -- scenarios with a single resource DLL wouldn't need this 

There are many member functions and constructors for the String class that take up to 10 arguments. Ultimately, they call CentralFormat, which does the real job.

 class String : public CString { public: String() { } String(WORD stringTableID, HINSTANCE hInst = GetModuleHInst()) { Format(stringTableID, hInst); } String(WORD stringTableID, const StringArg& arg1, HINSTANCE hInst = GetModuleHInst()) { Format(stringTableID, arg1, hInst); } String(WORD stringTableID, const StringArg& arg1, const StringArg& arg2, HINSTANCE hInst = GetModuleHInst()) { Format(stringTableID, arg1, arg2, hInst); } String(WORD stringTableID, const StringArg& arg1, const StringArg& arg2, const StringArg& arg3, HINSTANCE hInst = GetModuleHInst()) { Format(stringTableID, arg1, arg2, arg3, hInst); } String(WORD stringTableID, const StringArg& arg1, const StringArg& arg2, const StringArg& arg3, const StringArg& arg4, HINSTANCE hInst = GetModuleHInst()) { Format(stringTableID, arg1, arg2, arg3, arg4, hInst); } String(WORD stringTableID, const StringArg& arg1, const StringArg& arg2, const StringArg& arg3, const StringArg& arg4, const StringArg& arg5, HINSTANCE hInst = GetModuleHInst()) { Format(stringTableID, arg1, arg2, arg3, arg4, arg5, hInst); } String(WORD stringTableID, const StringArg& arg1, const StringArg& arg2, const StringArg& arg3, const StringArg& arg4, const StringArg& arg5, const StringArg& arg6, HINSTANCE hInst = GetModuleHInst()) { Format(stringTableID, arg1, arg2, arg3, arg4, arg5, arg6, hInst); } String(WORD stringTableID, const StringArg& arg1, const StringArg& arg2, const StringArg& arg3, const StringArg& arg4, const StringArg& arg5, const StringArg& arg6, const StringArg& arg7, HINSTANCE hInst = GetModuleHInst()) { Format(stringTableID, arg1, arg2, arg3, arg4, arg5, arg6, arg7, hInst); } String(WORD stringTableID, const StringArg& arg1, const StringArg& arg2, const StringArg& arg3, const StringArg& arg4, const StringArg& arg5, const StringArg& arg6, const StringArg& arg7, const StringArg& arg8, HINSTANCE hInst = GetModuleHInst()) { Format(stringTableID, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, hInst); } String(WORD stringTableID, const StringArg& arg1, const StringArg& arg2, const StringArg& arg3, const StringArg& arg4, const StringArg& arg5, const StringArg& arg6, const StringArg& arg7, const StringArg& arg8, const StringArg& arg9, HINSTANCE hInst = GetModuleHInst()) { Format(stringTableID, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, hInst); } CString& Format(WORD stringTableID, HINSTANCE hInst = GetModuleHInst()); CString& Format(WORD stringTableID, const StringArg& arg1, HINSTANCE hInst = GetModuleHInst()); CString& Format(WORD stringTableID, const StringArg& arg1, const StringArg& arg2, HINSTANCE hInst = GetModuleHInst()); CString& Format(WORD stringTableID, const StringArg& arg1, const StringArg& arg2, const StringArg& arg3, HINSTANCE hInst = GetModuleHInst()); CString& Format(WORD stringTableID, const StringArg& arg1, const StringArg& arg2, const StringArg& arg3, const StringArg& arg4, HINSTANCE hInst = GetModuleHInst()); CString& Format(WORD stringTableID, const StringArg& arg1, const StringArg& arg2, const StringArg& arg3, const StringArg& arg4, const StringArg& arg5, HINSTANCE hInst = GetModuleHInst()); CString& Format(WORD stringTableID, const StringArg& arg1, const StringArg& arg2, const StringArg& arg3, const StringArg& arg4, const StringArg& arg5, const StringArg& arg6, HINSTANCE hInst = GetModuleHInst()); CString& Format(WORD stringTableID, const StringArg& arg1, const StringArg& arg2, const StringArg& arg3, const StringArg& arg4, const StringArg& arg5, const StringArg& arg6, const StringArg& arg7, HINSTANCE hInst = GetModuleHInst()); CString& Format(WORD stringTableID, const StringArg& arg1, const StringArg& arg2, const StringArg& arg3, const StringArg& arg4, const StringArg& arg5, const StringArg& arg6, const StringArg& arg7, const StringArg& arg8, HINSTANCE hInst = GetModuleHInst()); CString& Format(WORD stringTableID, const StringArg& arg1, const StringArg& arg2, const StringArg& arg3, const StringArg& arg4, const StringArg& arg5, const StringArg& arg6, const StringArg& arg7, const StringArg& arg8, const StringArg& arg9, HINSTANCE hInst = GetModuleHInst()); private: void CentralFormat(WORD stringTableID, std::vector<const StringArg*>& args, HINSTANCE hInst); }; 

Finally, the implementation (I hope itโ€™s normal to post this a lot on StackOverflow, although the bulk of it is very simple):

 StringArg::StringArg(LPCWSTR val) { m_strVal = val; } StringArg::StringArg(const CString& val) { m_strVal = (LPCWSTR)val; } StringArg::StringArg(int val, LPCWSTR formatSpec) { if(NULL == formatSpec) formatSpec = L"%d"; //GLOK m_strVal.Format(formatSpec, val); } StringArg::StringArg(size_t val, LPCWSTR formatSpec) { if(NULL == formatSpec) formatSpec = L"%u"; //GLOK m_strVal.Format(formatSpec, val); } StringArg::StringArg(WORD val, LPCWSTR formatSpec) { if(NULL == formatSpec) formatSpec = L"%u"; //GLOK m_strVal.Format(formatSpec, val); } StringArg::StringArg(DWORD val, LPCWSTR formatSpec) { if(NULL == formatSpec) formatSpec = L"%u"; //GLOK m_strVal.Format(formatSpec, val); } StringArg::StringArg(__int64 val, LPCWSTR formatSpec) { if(NULL == formatSpec) formatSpec = L"%I64d"; //GLOK m_strVal.Format(formatSpec, val); } StringArg::StringArg(double val, LPCWSTR formatSpec) { if(NULL == formatSpec) formatSpec = L"%f"; //GLOK m_strVal.Format(formatSpec, val); } CString StringArg::ToString() const { return m_strVal; } void String::CentralFormat(WORD stringTableID, std::vector<const StringArg*>& args, HINSTANCE hInst) { size_t argsCount = args.size(); _ASSERT(argsCount < 10); //code below assumes a single character position indicator CString tmp; HINSTANCE hOld = AfxGetResourceHandle(); AfxSetResourceHandle(hInst); BOOL b = tmp.LoadString(stringTableID); AfxSetResourceHandle(hOld); if(FALSE == b) { #ifdef _DEBUG //missing string resource, or more likely a bad stringID was used -- tell someone!! CString s; s.Format(L"StringID %d could not be found! %s", stringTableID, hInst == ghCommonHInst ? L"CommonHInst was passed in" : L"CommonHInst was NOT passed in"); //GLOK ::MessageBeep(MB_ICONHAND); ::MessageBeep(MB_ICONEXCLAMATION); ::MessageBeep(MB_ICONHAND); _ASSERT(0); ::MessageBox(NULL, s, L"DEBUG Error - Inform Development", MB_ICONSTOP | MB_OK | MB_SERVICE_NOTIFICATION); //GLOK } #endif //_DEBUG CString::Format(L"(???+%d)", stringTableID); //GLOK return; } //check for the degenerate case if(0 == argsCount) { CString::operator=(tmp); return; } GetBuffer(tmp.GetLength() * 3); //pre-allocate space ReleaseBuffer(0); LPCWSTR pStr = tmp; while(L'\0' != *pStr) { bool bSkip = false; if(L'{' == *pStr) { //is this an incoming string position? //we only support 10 args, so the next char must be a number if(wcschr(L"0123456789", *(pStr + 1))) //GLOK { if(L'}' == *(pStr + 2)) //and closing brace? { bSkip = true; //this is a replacement size_t index = *(pStr + 1) - L'0'; _ASSERT(index < argsCount); _ASSERT(index >= 0); if((index >= 0) && (index < argsCount)) CString::operator+=(args[index]->ToString()); else { //bad positional index CString msg; msg.Format(L"(??-%d)", index); //GLOK CString::operator+=(msg); } pStr += 2; //get past the two extra characters that we skipped ahead and peeked at } } } if(false == bSkip) CString::operator+=(*pStr); pStr++; } } CString& String::Format(WORD stringTableID, HINSTANCE hInst) { std::vector<const StringArg*> args; CentralFormat(stringTableID, args, hInst); return *this; } CString& String::Format(WORD stringTableID, const StringArg& arg1, HINSTANCE hInst) { std::vector<const StringArg*> args; args.push_back(&arg1); CentralFormat(stringTableID, args, hInst); return *this; } CString& String::Format(WORD stringTableID, const StringArg& arg1, const StringArg& arg2, HINSTANCE hInst) { std::vector<const StringArg*> args; args.push_back(&arg1); args.push_back(&arg2); CentralFormat(stringTableID, args, hInst); return *this; } CString& String::Format(WORD stringTableID, const StringArg& arg1, const StringArg& arg2, const StringArg& arg3, HINSTANCE hInst) { std::vector<const StringArg*> args; args.push_back(&arg1); args.push_back(&arg2); args.push_back(&arg3); CentralFormat(stringTableID, args, hInst); return *this; } CString& String::Format(WORD stringTableID, const StringArg& arg1, const StringArg& arg2, const StringArg& arg3, const StringArg& arg4, HINSTANCE hInst) { std::vector<const StringArg*> args; args.push_back(&arg1); args.push_back(&arg2); args.push_back(&arg3); args.push_back(&arg4); CentralFormat(stringTableID, args, hInst); return *this; } CString& String::Format(WORD stringTableID, const StringArg& arg1, const StringArg& arg2, const StringArg& arg3, const StringArg& arg4, const StringArg& arg5, HINSTANCE hInst) { std::vector<const StringArg*> args; args.push_back(&arg1); args.push_back(&arg2); args.push_back(&arg3); args.push_back(&arg4); args.push_back(&arg5); CentralFormat(stringTableID, args, hInst); return *this; } CString& String::Format(WORD stringTableID, const StringArg& arg1, const StringArg& arg2, const StringArg& arg3, const StringArg& arg4, const StringArg& arg5, const StringArg& arg6, HINSTANCE hInst) { std::vector<const StringArg*> args; args.push_back(&arg1); args.push_back(&arg2); args.push_back(&arg3); args.push_back(&arg4); args.push_back(&arg5); args.push_back(&arg6); CentralFormat(stringTableID, args, hInst); return *this; } CString& String::Format(WORD stringTableID, const StringArg& arg1, const StringArg& arg2, const StringArg& arg3, const StringArg& arg4, const StringArg& arg5, const StringArg& arg6, const StringArg& arg7, HINSTANCE hInst) { std::vector<const StringArg*> args; args.push_back(&arg1); args.push_back(&arg2); args.push_back(&arg3); args.push_back(&arg4); args.push_back(&arg5); args.push_back(&arg6); args.push_back(&arg7); CentralFormat(stringTableID, args, hInst); return *this; } CString& String::Format(WORD stringTableID, const StringArg& arg1, const StringArg& arg2, const StringArg& arg3, const StringArg& arg4, const StringArg& arg5, const StringArg& arg6, const StringArg& arg7, const StringArg& arg8, HINSTANCE hInst) { std::vector<const StringArg*> args; args.push_back(&arg1); args.push_back(&arg2); args.push_back(&arg3); args.push_back(&arg4); args.push_back(&arg5); args.push_back(&arg6); args.push_back(&arg7); args.push_back(&arg8); CentralFormat(stringTableID, args, hInst); return *this; } CString& String::Format(WORD stringTableID, const StringArg& arg1, const StringArg& arg2, const StringArg& arg3, const StringArg& arg4, const StringArg& arg5, const StringArg& arg6, const StringArg& arg7, const StringArg& arg8, const StringArg& arg9, HINSTANCE hInst) { std::vector<const StringArg*> args; args.push_back(&arg1); args.push_back(&arg2); args.push_back(&arg3); args.push_back(&arg4); args.push_back(&arg5); args.push_back(&arg6); args.push_back(&arg7); args.push_back(&arg8); args.push_back(&arg9); CentralFormat(stringTableID, args, hInst); return *this; } 
+3
Mar 26 '09 at 13:52
source share

Look at the boost library.

+30
Jan 20 '09 at 18:54
source share

QT QString allows you to do the following:

 QString("Hi there %1. You are %2 years old. How does it feel \ to be %2?").arg(name).arg(age) 
+15
Jan 20 '09 at 19:20
source share

Believe it or not, printf and friends support positional arguments.

  #include <stdio.h> int main() { char *name = "Logan"; int age = 25; printf("Hi there %1$s, you are %2$d years old. How does it feel to be %2$d?\n", name, age); return 0; } 
+9
Mar 08 '09 at 18:38
source share

You might look at FastFormat- library.

+7
Jan 21 '09 at 9:36
source share

I think you can use FastFormat since

 std::string result; fastformat::fmt(result, "Hi there {0}. You are {1} years old. How does it feel to be {1}?", name, age); 

which is almost identical syntax.

+3
Mar 16 '09 at 9:05
source share

Several parameters:

  • formatted formatting library (already mentioned)
  • stringstreams
  • legacy printf / sprintf functions
  • custom implementation using regular expressions or inline string functions

In the corresponding note, what you are saying is completely inadequate for localization.

0
Jan 20 '09 at 19:23
source share

If you intend to write yourself, searching and replacing is probably not the best approach, since most search / replace methods allow you to replace one at a time and do very poor job of escpae character resolution (for example, if you want to include the personal string {0} in your conclusion.

You are much better off writing your own state machine to go through the input line, generating an output line "on the fly" in one pass. This allows you to handle escape characters and more complex output functions (for example, localized dates {0:dd\MM\yyyy} ). This will give you more flexibility in addition to being faster than a search / replace or regex approach.

0
Jan 20 '09 at 19:24
source share

iostream:

 stringstream s; string a; s << "this is string a: " << a << endl; 

You can format it as sprintf (google for "iostream format") and its standard in C ++.

0
Jan 20 '09 at 20:53
source share

Customize Windows? FormatMessage () is your friend

0
Jan 22 '09 at 21:04
source share

If you have to be cross-platform, then I will vote for forcing :: format, or perhaps in ICU. If you should only support Windows, then FormatMessage (or a convenient wrapper for this, CString :: FormatMessage, if you use MFC)

You can look here for comparison: http://www.mihai-nita.net/article.php?artID=20060430a

0
Nov 10 '09 at
source share

In addition to the options suggested by others, I can recommend the fmt library , which implements string formatting like str.format in Python and String.Format in C #. Here is an example:

 string result = fmt::format("Hi {0}. You are {1} years old.", name, age); 

The library is completely safe and much faster than the Boost Format.

Disclaimer: I am the author of this library.

0
Dec 26 '12 at 1:20
source share

Some time ago, I tried to do something similar, but with some additional assumptions:

  • no support for positional formatting (so I think this is not for you)
  • C ++ 2k3 (to include it in some old codes)
  • (almost) no dependencies (even crt, so no sprintf dependencies)

I failed and it is not completely finished, but you can still see some results:

http://code.google.com/p/pileofcrap/source/browse/tests_format.cpp

http://code.google.com/p/pileofcrap/source/browse/format/Format.h

0
Apr 01 '13 at 16:47
source share



All Articles