views:

6584

answers:

12

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

String.Format("Hi there {0}. You are {1} years old. How does it feel to be {1}?", name, age);

This is needed because we're going to try and make it easier to localize our app, and giving the translators {0} and {1} to position anywhere in the sentence is much easier than giving them a %s, %d, %d which must be positioned in that order in their translation.

I suppose search and replace with variable inputs (va_start, va_end, etc) is what I'll end up building, but if there is already a solid solution, that would be preferrable.

Thanks :)

+24  A: 

Look at the boost format library.

Eddie Parker
+9  A: 

QT's QString allows you do to this:

QString("Hi there %1. You are %2 years old. How does it feel \
         to be %2?").arg(name).arg(age)
shoosh
QtCore supports internationalization with QObject::tr. It returns a QString with the %1, %2, etc. placeholders. It also allows for a special %n which allows words to be pluralized properly.
strager
A: 

Several options:

  • boost format library (already mentioned)
  • stringstreams
  • legacy printf/sprintf functions
  • custom implementation using regular expressions or built-in string functions

On a related note, what you're talking about is completely inadequate for localization.

Joel Coehoorn
Why do you say so? The string example above will be pulled out into a resource file, so we want something that can be fed back into a function with the correct inputs. Naturally we also need to handle date/time issues, etc.
DougN
It also ignores left to right/right to left rules, but if you the literal itself is in a resource file you might be okay. If the _only_ thing you were doing was substituting translated words, you'd have a real problem.
Joel Coehoorn
Ah, right. I'm pulling in strings from resources. That, plus positional replacements and it should all work.
DougN
A: 

If you're going to be writing your own, search and replace is probably not the best approach, since most search/replace methods only let you replace one at a time, and do a very poor job of allowing escpae characters (like if you want to include the literal string {0} in your output.

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

Eclipse
A: 

iostream:

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

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

Rodrigo Strauss
He's looking for positional arguments, like in C#.
Eddie Parker
"positional" in the sense that he can say 'printPositional("{0} this is string a {1}", s, a)' *and* 'printPositional("{1} this is string b {0}", b, s)' and get the correct results.
Max Lybbert
Using stringstream does work (and also supports many other data types), so +1 -- but, I find it can get very messy when you want to combine many variables, which is why it's neater to use sprintf (even though it's from the C lib). That said, sprintf does have a drawback; you need to use a buffer with a set size. So boost is the best alternative I think.
nbolton
+3  A: 

You might have a look at the FastFormat-library.

Christof Schardt
A: 

Targetting Windows? FormatMessage() is your friend

Serge - appTranslator
+4  A: 

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;
 }
Logan Capaldo
This is not standard, though, right?
dcw
The wonderful thing about standards is that there are so many to choose from. E.g. http://www.opengroup.org/onlinepubs/000095399/functions/printf.html it is an XSI standard.
Logan Capaldo
+1  A: 

I think you can use FastFormat, as

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 the identical syntax.

dcw
A: 

Lots of good recommendations above that would work in most situations. In my case, I ultimately wanted to load strings from a resource, AND keep the string resources as close to .NET String.Format as I could, so I rolled my own. After looking at some of the implementations above for ideas, the resulting implementation was quite short and easy.

There is a class String, which in my case derives from Microsoft's CString, but it could derive from about any string class. There is also a class StringArg -- it's job is to take any parameter type and turn it into a string (ie it mimics ToString in .NET). If a new object needs to be ToString'd, you just add another constructor. The constructor allows a printf-style format specifier for non-default formatting.

The String class then accepts a string table ID for the source string, a number of StringArg parameters, and finally an optional HINSTANCE (I use lots of DLLs, any of which could host the string table, so this allowed me to pass it in, or use a DLL-specific HINSTANCE by default).

Usage examples:

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

As it is, it only takes a string ID for an input, but it would be trivial to add an input string instead of a string ID:

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 does the equivalent of ToString on 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's own HINSTANCE -- scenarios with a single resource DLL wouldn't need this

For the String class, there are a bunch of member functions and constructors that take up to 10 arguments. These ultimately call CentralFormat which does the real work.

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 (hopefully it's OK to post this much 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;
}
DougN
A: 

You can use the standard C function printf(...)

printf( "Hello, my name is %s, I am %d years old.", "Babak", 27 );

would return "Hello, my name is Babak, I am 27 years old."

Babak Naffas
A: 

If you should be cross-platform, then I would vote for boost::format, or maybe ICU. If you should only support Windows, then FormatMessage (or the convenient wrapper of that, CString::FormatMessage, if you use MFC)

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

Mihai Nita