1

Fold-ish expressions in C++11 & C++14: idiomatic approach?

The accepted answer of the Q&A Variadic template pack expansion makes use of a common pre-C++17 (prior to fold expressions) approach to "folding" of an unexpanded template parameter pack.

I've seen a few different variations of this technique; taking the Q&A above as an example:

#include <initializer_list>
#include <iostream>
#include <utility>

template <typename T> static void bar(T) {}

template <typename... Args> static void foo1(Args &&... args) {
  using expander = int[];
  // Left-most void to avoid `expression result unused [-Wunused-value]`
  (void)expander{0, ((void)bar(std::forward<Args>(args)), 0)...};
}

template <typename... Args> static void foo2(Args &&... args) {
  int dummy[] = {0, ((void)bar(std::forward<Args>(args)), 0)...};
  // To avoid `unused variable 'dummy' [-Wunused-variable]`
  (void)dummy;
}

template <typename... Args> static void foo3(Args &&... args) {
  // Left-most void to avoid `expression result unused [-Wunused-value]`
  (void)std::initializer_list<int>{((void)bar(std::forward<Args>(args)), 0)...};
}

template <typename... Args> static void foo4(Args &&... args) {
  auto l = {0, ((void)bar(std::forward<Args>(args)), 0)...};
  // To avoid `unused variable 'l' [-Wunused-variable]`
  (void)l;
}

int main() {
  foo1(1, 2, 3, "3");
  foo1();
  foo2(1, 2, 3, "3");
  foo2();
  foo3(1, 2, 3, "3");
  foo3();
  foo4(1, 2, 3, "3");
  foo4();
  return 0;
}

Are any of these variations (or other variations) considered "the idiomatic one"? Are there any subtleties/differences between them that one would need to take care with?

The std::initializer_list approach does not require the somewhat elusive left-most 0 in the braced-init-list, as an initializer list may be empty, whereas an array may not be zero(/negative)-sized. Possibly this could be an argument for foo3 (arguably slightly less complexity at the cost of an additional #include).

dfrib
  • 70,367
  • 12
  • 127
  • 192
  • Is order of exectuion guaranteed for all variations? (I think it is for foo3, have to search for the source of my belief.) –  Oct 28 '19 at 10:17
  • 1
    @generic_opto_guy all variations make used of a _braced-init-list_ with entries separated by the comma operator; the former guarantees evaluation in order of appearance ([dcl.init.list]) and the latter is evaluated left-to-right ([expr.comma]), so I believe the order of execution is guaranteed (and the same) for all variations. – dfrib Oct 28 '19 at 10:20

1 Answers1

2

Are any of these variations (or other variations) considered "the idiomatic one"?

I would say yes.

Are there any subtleties/differences between them that one would need to take care with?

There are mostly equivalent, but

foo3 and foo4 require #include <initializer_list>
whereas foo1 and foo2 don't.

Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • Thanks for the answer. On the other hand, the `std::initializer_list` approach does not require the somewhat elusive left-most `0` value in the _braced-init-list_. – dfrib Oct 28 '19 at 11:58