TheLoneMilkMan
TheLoneMilkMan

Reputation: 243

Pass Groups of Arguments in a Variadic Template

Variadic templates are really useful for doing recursive operations. In this case I'd like each recursive call to operate on two arguments so I don't have to repeatedly call the same function. To achieve this I can write:

f() {}

template<typename M,
         typename N,
         typename... Rest>
f(M arg1, N arg2, Rest... rest)
{
    doStuff(arg1, arg2);
    f(rest);
}

Then I would call this as such:

f(arg1a, arg1b,
  arg2a, arg2b,
  arg3a, arg3b);

However, if the call is not formatted so nicely, and all the arguments are on one line, or the column is split at the wrong point, it becomes quite unreadable. Especially so if the call might contain a dozen or so pairs. I attempted to remedy this by requiring a parameter pack of pairs to be passed in. I would like the function to have to be called like this:

f({arg1a, arg1b},
  {arg2a, arg2b},
  {arg3a, arg3b});

This seems to be mostly failing as the initializer list does not get deduced to a pair. I can call make_pair on each set of arguments, but that just solves a readability problem with another readability problem. Is there any way to get this sort of calling syntax to work? The arguments are not the same across pairs or within pairs.

Upvotes: 4

Views: 355

Answers (2)

Nir Friedman
Nir Friedman

Reputation: 17704

It may seem surprising but the best solution I've found for this is actually a "blast from the past" that doesn't use variadics at all.

struct F {
    template <class M, class N>
    F& operator()(M arg1, N arg2) { 
        doStuff(arg1, arg2);
        return *this;
    }
};

F{} (arg1a, arg1b)
    (arg2a, arg2b);

And so on. Although, I'll also note that clang-format does not format this nicely. So I ended up doing something slightly different again.

Upvotes: 1

Yuki
Yuki

Reputation: 4173

Sorry, it is not possible: an implicit type conversion from braced list to a user-defined type. And the only conversion that is possible is for std::initializer_list, but it can be only done for the same types, or for convertible types.

With this said, I would offer this, as a possibility,

template <typename M, typename N>
void doStuff(M&&, N&&) {}

template <typename M, typename N>
struct MyRepeater;

template <typename M, typename N>
MyRepeater<M, N> f(M&& one, N&& two);

template <typename M, typename N>
struct MyRepeater {
  template <typename I, typename J>
  MyRepeater<I, J> operator()(I&& one, J&& two) const {
    return f(std::forward<I>(one), std::forward<J>(two));
  }
};

template <typename M, typename N>
MyRepeater<M, N> f(M&& one, N&& two) {
  doStuff(one, two);
  return MyRepeater<M, N>();
}

int main() {
  f(Foo1(), Foo2())(Bar1(), Bar2())(Bar2(), Foo1());
}

Of course, the downside is that only for readability you have two write some extra code (in my opinion, it is not a bad motivation).

Upvotes: 2

Related Questions