Re: C++11 Variadic Templates: Format("I %%% about %%% of
%%%","dream", 7, 9) == "I dream about 7 of 9"
On 2011-11-28 23:31, Andrew Tomazos wrote:
I wanted to write a function that takes a string containing a number
of placeholders and a corresponding number of other parameters
(perhaps non-pod) and returns a string with the placeholders replaced
with stringified versions of the parameters:
Yes, the idea is nice, but let me comment on some parts.
I haven't used Variadic Templates before. My first draft is below. It
appears to work, but it's using a linear recursion. My question is,
is there a way to write it iteratively?
Yes, see below.
#include<string>
#include<sstream>
#include<iostream>
using namespace std;
class StringFormatException {};
const string g_sPlaceholder("%%%");
template<class ... T>
inline string Format(const string& sFormat, const T& ...);
This function template is no-where use. You should get rid of it.
template<class ... T>
inline string Format(const string& sFormat)
There is no reason to declare this as a variadic template. Just declare
it as a non-template function:
inline string Format(const string& sFormat)
{
size_t iFirstPlaceholderPos = sFormat.find(g_sPlaceholder);
Either use string::size_type or - more preferably auto to determine the
type of iFirstPlaceholderPos. In theory there is no guarantee that
size_t and string::size_type have the same type or even the same maximum
value (which is relevant when using string::npos).
if (iFirstPlaceholderPos != string::npos)
throw StringFormatException();
return sFormat;
}
template<class Head, class ... Tail>
inline string Format(const string& sFormat, const Head& head, const
Tail& ... tail)
{
stringstream os;
size_t iFirstPlaceholderPos = sFormat.find(g_sPlaceholder);
Same problem here.
if (iFirstPlaceholderPos == string::npos)
throw StringFormatException();
else
{
string sFront(sFormat, 0, iFirstPlaceholderPos);
string sBack(sFormat, iFirstPlaceholderPos +
g_sPlaceholder.size());
os<< sFront<< head<< Format(sBack, tail ... );
}
return os.str();
}
int main()
{
if (Format("I %%% about %%% of %%%","dream", 7, 9) == "I dream
about 7 of 9")
cout<< "pass"<< endl;
else
cout<< "fail"<< endl;
return 0;
}
In regard to your question, whether an iterative solution is possible:
Yes, at least in the sense that you can serialize your expansions. The
following code demonstrates this:
//----------------------------
#include <string>
#include <sstream>
#include <iostream>
#include <cstddef>
class StringFormatException {};
const std::string g_sPlaceholder("%%%");
struct void_{};
template<class T>
void_ DoFormat(std::ostream& os, const std::string& sFormat,
std::string::size_type& curr, const T& t)
{
auto nextPos = sFormat.find(g_sPlaceholder, curr);
if (nextPos == std::string::npos)
throw StringFormatException();
else
{
os.write(&sFormat.front() + curr, nextPos - curr);
os << t;
curr = nextPos + g_sPlaceholder.size();
}
return {};
}
template <class... Args>
inline std::string Format(const std::string& sFormat, const Args&... args)
{
std::stringstream os;
std::string::size_type curr = 0;
std::initializer_list<void_>{DoFormat(os, sFormat, curr, args)...};
auto nextPos = sFormat.find(g_sPlaceholder, curr);
if (nextPos != std::string::npos)
throw StringFormatException();
if (!sFormat.empty())
os.write(&sFormat.front() + curr, sFormat.size() - curr);
return os.str();
}
// ... Your test main() follows here unchanged
//----------------------------
You may wonder about the astonishing void_ type as well as the
std::initializer_list<void_>{DoFormat(os, sFormat, curr, args)...}
expression.
What I'm realizing here is to create an initializer-list expanded with
sizeof...(Args) expressions that have type void_. Unfortunately I cannot
use void here, because this is no object type. Even more unfortunate is,
that I cannot use a function call e.g.
template<class... T>
void swallow(T&&...){}
....
swallow(DoFormat(os, sFormat, curr, args)...);
This will compile, but since the order of the evaluation of function
arguments is not determined, there is no guarantee that this works in a
portable manner, because each function call modifies the arguments os
and curr. There exists a special rule in C++11 that ensures that
initializer-lists are evaluated in a strict order, this is the reason
why I'm creating an otherwise unused std::initializer_list<void_> here.
As elegant as it looks, I'm very unhappy about the need to create such a
hack here. It would be great, if there would exist a further expansion
pattern that allows me to write e.g.
DoFormat(os, sFormat, curr, args)...;
such that DoFormat may have type void as well.
I hope nonetheless that this suggestion gives you another idea how to
proceed.
HTH & Greetings from Bremen,
Daniel Kr?gler
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]