7

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.

rationalcoder
  • 1,587
  • 1
  • 15
  • 29
  • Possible if you pass a va_arg to model the variable part. Old-fashioned though. – Bathsheba Aug 03 '15 at 18:44
  • can you change the signature, i.e the order of the parameters? – m.s. Aug 03 '15 at 18:50
  • @m.s Nope, that would be too convenient wouldn't it? ;) – rationalcoder Aug 03 '15 at 18:51
  • @Bathsheba Can you elaborate? – rationalcoder Aug 03 '15 at 18:51
  • 2
    @noobEqualsBlake [like here?](http://coliru.stacked-crooked.com/a/861af17b9c33e736) – Piotr Skotnicki Aug 03 '15 at 19:06
  • @PiotrSkotnicki: Briliant! Where do you learn these things? I have some basic knowledge about STL and a few C++11 concepts. But this looks beautiful. – Martijn Courteaux Aug 03 '15 at 19:18
  • @PiotrSkotnicki Yes. I have no idea how it works, but it looks like it is accomplishing the right thing. Post it as an answer with a detailed explanation and I will accept it as an answer. – rationalcoder Aug 03 '15 at 19:27
  • There may be a way to do this using a variadic template, `std::tuple`, and `sizeof(...)`, but I would be worried about using convoluted code to break typical coding conventions. I would change the signature, or just call a variadic version from the static versions. – Jason Aug 03 '15 at 19:34
  • An added benefit of re-using the static argument methods and adding a true variadic one is that people can and would need to use the proper variadic function if they need more arguments than the static argument ones. – Jason Aug 03 '15 at 19:55
  • 1
    @MartijnCourteaux: *"Where do you learn these things?"* - here on StackOverflow – Piotr Skotnicki Aug 04 '15 at 05:50

3 Answers3

6

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)...); 
}

DEMO

Community
  • 1
  • 1
Piotr Skotnicki
  • 46,953
  • 7
  • 118
  • 160
  • I just noticed that it required c++14. This is cool and all (+1), but I need a solution using c++11 and or boost (I will edit the question to make that more clear). I will keep the question unanswered for a little while, but if no one comes up with another solution, I will mark this as the accepted answer. – rationalcoder Aug 03 '15 at 19:45
  • 1
    @noobEqualsBlake it can be [implemented in C++11](http://stackoverflow.com/a/17426611/3953764) as well – Piotr Skotnicki Aug 03 '15 at 19:53
  • Sheesh. Hate to ask more of you, but considering I am barely grasping this example, could you please incorporate that one or a similar implementation into this answer. Otherwise, I won't be able to figure out how to use this stuff on my own. – rationalcoder Aug 03 '15 at 20:11
  • Exactly what I was looking for! Elegant too. Thanks a lot! – rationalcoder Aug 03 '15 at 20:20
  • The answer is sufficient for me as it is. However, if you could elaborate on the part where you talked about supporting the default argument on the end, that would be great. I understand if you feel like that would waste your time. – rationalcoder Aug 03 '15 at 20:35
0

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

A Hernandez
  • 484
  • 2
  • 7
0

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

Jason
  • 3,777
  • 14
  • 27
  • I ended up using the initializer list version I mentioned in my question, and called that from all of the previous functions. Its cleaner than using templates at all. However, I specifically asked for template and or macro hacks to solve the problem, so Piotr takes the cake on this one :) – rationalcoder Aug 04 '15 at 00:30
  • I only posted so there's another possible solution if anyone else reads. Actually, I think Piotr's solution can be simplified. – Jason Aug 04 '15 at 03:15
  • Then prove it and post it ;) – rationalcoder Aug 04 '15 at 03:46