Reputation: 1687
I have stumbled upon old code that looks like this:
void dothing(bool testBool,
const std::string& testString1,
const std::string& file,
int line,
const std::string& defaultString = "")
{
// do something...
}
void dothings(bool testBool,
const std::string& testString1,
const std::string& testString2,
const std::string& file,
int line,
const std::string& defaultString = "")
{
dothing(testBool, testString1, file, line, defaultString);
dothing(testBool, testString2, file, line, defaultString);
}
void dothings(bool testBool,
const std::string& testString1,
const std::string& testString2,
const std::string& testString3,
const std::string& file,
int line,
const std::string& defaultString = "")
{
dothings(testBool, testString1, testString2, file, line, defaultString);
dothing(testBool, testString3, file, line, defaultString);
}
void dothings(bool testBool,
const std::string& testString1,
const std::string& testString2,
const std::string& testString3,
const std::string& testString4,
const std::string& file,
int line,
const std::string& defaultString = "")
{
dothings(testBool, testString1, testString2, testString3, file, line, defaultString);
dothing(testBool, testString1, file, line, defaultString);
}
It is ridiculous and I am trying to refactor it to be:
void dothings(bool testBool,
std::initializer_list<std::string> testStrings,
const std::string& file,
int line,
const std::string& defaultString = "")
{
for(auto iter = testStrings.begin(); iter != testStrings.end(); ++iter)
{
dothing(testBool, *iter, file, line, defaultString);
}
}
The problem is that those functions are used a lot and I would like to write a macro or template in such a way that all of the previous functions construct an initializer list of strings of all of the test strings and pass them to the one new function. I want to write something like this:
#define dothings(testBool, (args), file, line) dothings(testBool, {args}, file, line)
I don't really care about the default string in these functions, but if there is a way to support it, that would be great.
I have access to a c++11 compiler and boost ONLY.
I cannot reorder the arguments to these functions.
I have seen some interesting posts about variable argument macros, but It's just not clicking how to apply them to this case.
Upvotes: 7
Views: 252
Reputation: 3917
Here is what I mentioned earlier. Instead of jumping through template hoops, you could just write a normal variadic function that's called from the existing static argument functions. The lingua franca generally doesn't support variable arguments in the middle of argument lists, and it seems like it could be a bit of a bad idea to encourage it.
The variadic function could look as follows...
template <typename T, typename = typename std::enable_if<std::is_same<T, std::string>::value>::type>
void dothings(bool testBool,
const std::string& file,
int line,
const std::string& defaultString,
T t) {
dothing(testBool, t, file,line, defaultString);
}
template <typename T, typename... Ts>
void dothings(bool testBool,
const std::string& file,
int line,
const std::string& defaultString,
T t,
Ts... ts) {
dothing(testBool, file,line, defaultString, t);
dothing(testBool, file,line, defaultString, ts...);
}
Then just call the variadic function from the static argument functions...
void dothings(bool testBool,
const std::string& testString1,
const std::string& testString2,
const std::string& file,
int line,
const std::string& defaultString)
{
dothing(testBool, file, line, defaultString testString1, testString2);
}
etc...
This preserves compatibility, allows properish variadic use, and even encourages people to use it if they need more than n test strings. It's slightly more verbose, but arguably more people would be able to maintain it.
note: std::array
could eliminate the recursive template
Upvotes: 0
Reputation: 484
Assuming you can really not change the order of the argument, you can use two variadic template function:
int lastArgument(int testInt)
{
return testInt;
}
template<typename T, typename... Arguments>
int lastArgument(T t, Arguments... parameters)
{
return lastArgument(parameters...);
}
void someFunction(bool testBool, const string& test, int testInt)
{
//do something
}
template<typename T, typename... Arguments>
void someFunction(bool testBool, const T& test, Arguments... parameters)
{
int testInt = lastArgument(parameters...);
someFunction(testBool, test, testInt);
someFunction(testBool, parameters...);
}
You can do something similar to retrieve the last two parameters
Upvotes: 0
Reputation: 48487
This is just one of possible solutions, it can be improved to detect whether there is an additional defaulted string at the end or not (by means of some other metaprogramming technique together with SFINAE). This one exploits the indices trick to split the arguments list into two subsequences: one for the three trailing parameters, and one for the strings themselves. Eventually, each string is paired with the remaining arguments and calls to function dothing
are expanded.
void dothing(bool testBool
, const std::string& str
, const std::string& file
, int line
, const std::string& defaultString)
{
// processing of a single str
}
template <typename... Args, std::size_t... Is>
void dothings(bool testBool, std::index_sequence<Is...>, Args&&... args)
{
auto tuple = std::make_tuple(std::forward<Args>(args)...);
using expander = int[];
static_cast<void>(expander{ 0, (dothing(testBool, std::get<Is>(tuple)
, std::get<sizeof...(Args)-3>(tuple)
, std::get<sizeof...(Args)-2>(tuple)
, std::get<sizeof...(Args)-1>(tuple)), 0)... });
}
template <typename... Args>
void dothings(bool testBool, Args&&... args)
{
dothings(testBool
, std::make_index_sequence<sizeof...(Args)-3>{}
, std::forward<Args>(args)...);
}
Upvotes: 6