This is driving me insane. I just want to send a variadic list of pairs of strings into a template function that replaces words in a string, and the function works, but only with one parameter, not variadic.
This works fine
using std::string;
using spair = std::pair<string, string>;
string replace_all(string& str, const spair& sp)
{
string::size_type pos = 0;
string::size_type rmsize = std::get<0>(sp).size();
while((pos = str.find(std::get<0>(sp), pos)) != string::npos)
{
str.replace(pos, rmsize, std::get<1>(sp));
pos ;
}
return str;
}
string pattern = "My name is {{first_name}} {{last_name}} and I live in {{location}}";
string t = replace_all( pattern, {"{{first_name}}","Homer"} );
/*************************/
But this will not work
template<typename T=spair, typename ...Args>
string replace_all(string& s, const T& arg1, Args const&... args )
{
s = replace_all(s, arg1);
s = replace_all(s, args...);
return s;
}
string v = replace_all( pattern,
{"{{first_name}}","Homer"},
{"{{last_name}}","Simpson"},
{"{{location}}","Springfield"}
);
variadic.cpp:72:8: error: no matching function for call to **‘replace_all(std::string&, <brace-enclosed initializer list>, <brace-enclosed initializer list>, <brace-enclosed initializer list>)’
CodePudding user response:
There might be some abuse of initializer lists and templates here. To sum up:
- The internal type is known, it's always a pair of strings.
- The amount of "replace pairs" should be configurable
Using templates here, would force a new (function) type to be generated whenever replace_all
is called with a different number of pairs. You can greatly simplify if you rely on "runtime lists" like so:
string replace_all(string& s, std::vector<spair> const& v)
{
for (auto const& sp : v) replace_all(s, sp);
return s;
}
// call it like so
replace_all(pattern,
{
{"{{first_name}}","Homer"},
{"{{last_name}}","Simpson"},
{"{{location}}","Springfield"}
}
) << std::endl;
As @Sam Varshavchik mentions in the comments, doing pattern matching with the method you suggest is a bit of a problem. It could work if you call your function like so:
replace_all(pattern,
spair{"{{first_name}}","Homer"},
spair{"{{last_name}}","Simpson"},
spair{"{{location}}","Springfield"}
);
thus making the type to be deduced explicit.
CodePudding user response:
I could not get it exactly, but I devised a workaround:
// 1 to 5 replacement pairs
const spair emptyspair{{},{}}; // A "nothing" pair of empty strings
string replace_all(string& s,
const spair& sp1=emptyspair,
const spair& sp2=emptyspair,
const spair& sp3=emptyspair,
const spair& sp4=emptyspair,
const spair& sp5=emptyspair
)
{
std::array<spair,5> v{sp1, sp2, sp3, sp4, sp5};
for (spair sp : v )
{
if (sp != emptyspair)
{
auto& s1 = std::get<0>(sp);
auto& s2 = std::get<1>(sp);
s = replace_all(s, s1, s2);
}
}
return s;
}
This allows 1 to 5 spair arguments, defaulting to a dummy pair each that are ignored in the array that holds them. I think that all references bind to the same default object so do not cause 10 string objects and 5 pairs to be created, and the inequality check compares quickly to the same object.