Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can Template / Preprocessor hackery be used to support variable arguments in the middle of an argument list?

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.

like image 658
rationalcoder Avatar asked Aug 03 '15 18:08

rationalcoder


1 Answers

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

like image 182
Piotr Skotnicki Avatar answered Oct 03 '22 02:10

Piotr Skotnicki