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) {