String format function string Format(string format_string, T1 p1, T2 p2, ..., TN pn)


The Format() function takes a copy of format_string and replace occurrences of "___" with the remaining parameters to the function (p1, p2,...).  The first occurrence of "___" will be replaced by p1, the next by p2, and so on.  If there are no occurrences of "___" the remaining parameters will be appended to the string delimited by spaces.  If the parameters are not strings they will be converted to strings with operator&lt;&lt;(ostream,x).  Format will return the modified string.

For example:

Format("The ___ is ___ years", "fox", 8, "old")


evaluates to:


  "The fox is 8 years old"


The implementation follows:

struct FormatEmptyStruct {};

const string FormatPlaceholder("___");

template&lt;class T&gt;
inline FormatEmptyStruct OStreamWriteT(ostringstream&amp; os, const std::string&amp; sFormat, string::size_type&amp; iCurrentPos, const T&amp; t)
{
    auto iNextPos = sFormat.find(FormatPlaceholder, iCurrentPos);

    if (iNextPos == std::string::npos)
    {
        os.write(sFormat.data() + iCurrentPos, sFormat.size() - iCurrentPos);
        iCurrentPos = sFormat.size();

        os &lt;&lt; " ";

        os &lt;&lt; t;
    }
    else
    {
        os.write(sFormat.data() + iCurrentPos, iNextPos - iCurrentPos);

        os &lt;&lt; t;

        iCurrentPos = iNextPos + FormatPlaceholder.size();
    }
    return {};
}

struct EmptyStruct {};

inline string Format() { return ""; }

template &lt;class... Args&gt;
inline string Format(const string&amp; sFormat, const Args&amp;... args)
{
    ostringstream os;
    string::size_type iCurrentPos = 0;

    initializer_list&lt;FormatEmptyStruct&gt;{ OStreamWriteT(os, sFormat, iCurrentPos, args)... };

    if (!sFormat.empty())
        os.write(&amp;sFormat.front() + iCurrentPos, sFormat.size() - iCurrentPos);

    return os.str();
}
I can't fault the code (assuming it works). Looks good.

But what I would point is that to improve flexibility you should probably use numbered replacement sites.

The problem with format strings is that for I18N and L10N the format strings will be pulled out of the source code and placed in a separate resource file for translation. Unfortunately not all languages use the same noun verb ordering so for I18N to work efficiently you need a flexible placement strategy (so that you only need to change the string resource not the code).

English:

// "The disk named MyDisk contains 300 files."
"The disk named %1 contains %2 files" % DiskName % FileCount;


Basque:

// "300 fitxategi ditu, izendatutako MyDisk diskoa."
"%2 fitxategi ditu, izendatutako %1 diskoa." % DiskName % FileCount;


Or in your code:

std::cout &lt;&lt; Format(stringresource.get("DiskString"), DiskName, FileCount) &lt;&lt; std::endl;