int a[] { (functioncall(a1, a2), 0)...}; (void(a)); What does this syntax do/mean?
Asked Answered
E

1

2

I came across this post variadic template function to concatenate std::vector containers suggesting the use of the following syntax:

template<typename T>
void append_to_vector(std::vector<T>& v1, const std::vector<T>& v2) {
  std::cout << v2[0] << std::endl;
  for (auto& e : v2) v1.push_back(e);
}


template<typename T, typename... A>
std::vector<T> concat_version3(std::vector<T> v1, const A&... vr) {
    int unpack[] { (append_to_vector(v1, vr), 1)... };
    (void(unpack));
    return v1;
}

I started playing around with it to understand how it worked, since I haven't seen this:

int unpack[] { (append_to_vector(v1, vr), 0)... };
(void(unpack));

Seems like this is some kind of dynamically generated initialization list that also has side effects? I'm also puzzled by the fact that the 0 above doesn't matter. I substituted, -1 and 5, and each of these values worked just fine too.

So can someone tell me the name of this technique/syntax and what exactly is happening in the two lines above? I'd really appreciate any pointers and apologize if I missed the relevant SO posts.

Elliottellipse answered 10/11, 2015 at 0:17 Comment(1)
This technique achieves the purpose of calling the function append_to_vector(v1, X) once for each of the variadic arguments given, with X replaced by each element in turn. I call it the "dummy array comma operator hack", don't know if it has an official name :)Attila
H
5
int unpack[] { (append_to_vector(v1, vr), 1)... };
//        ^^ |                          |   ||| |    array of ints
//           ^                          |    |  ^    array initializer
//                                      ^    |       comma operator
//                                          ^^^      pack expansion

This is creating an array of ints containing as many elements as the size of the parameter pack vr. Each element in the array is 1, which is what the comma operator returns after evaluating both arguments. The final ellipsis indicates pack expansion of the parameter pack vr is being done.

So if you were to call your function as concat_version3(v1, v2, v3) where all arguments are vectors, then the above expression would result in

int unpack[]{ (append_to_vector(v1, v2), 1), (append_to_vector(v1, v3), 1) };

The nice thing about evaluating expressions within a braced-init-list is that the order of evaluation is fixed and happens left to right.

§8.5.4/4 [dcl.init.list]

Within the initializer-list of a braced-init-list, the initializer-clauses, including any that result from pack expansions (14.5.3), are evaluated in the order in which they appear.

So you're guaranteed that v2 gets appended to v1 before v3, which is what you want.


(void(unpack));

This is just a way to avoid unused variable warnings from the compiler.


Now, I would write your unpack initialization a bit differently.

int unpack[] { 1, (append_to_vector(v1, vr), 1)... };
//             ^^

In the original, if you called the function as concat_version3(v1), i.e. with an empty parameter pack, the code wouldn't compile because you'd be attempting to create a zero sized array, adding the extra element fixes that problem.

Furthermore, if you were using the above expression in more generic code where you didn't know what the return type of append_to_vector was, then you'd also need to guard against the possibility of it returning a type that overloads the comma operator. In that case you'd write

int unpack[] { 1, (append_to_vector(v1, vr), void(), 1)... };

By adding the void() expression in between you ensure that no overloaded comma operator is selected, and the built-in one is always called.


Finally, if you have a compiler that understands fold expressions, you can do away with the whole array trick and simply write

template<typename T, typename... A>
std::vector<T> concat_version3(std::vector<T> v1, const A&... vr)
{
    (void)(((append_to_vector(v1, vr), void()), ...));
    return v1;
}

Live demo

Note: the extra parentheses after the void cast are required due to a clang bug.

Hexaemeron answered 10/11, 2015 at 1:1 Comment(5)
Thanks a lot for this very thorough answer. Much obliged.Elliottellipse
The fold expression case could use a void cast too (to protect against overloaded commas).Weltpolitik
@Weltpolitik Thank you, didn't consider that possibility, somehow fold expressions seemed too cool to be susceptible to such pitfalls :) Fixed now.Hexaemeron
On another thread someone asked me why this wasn't supported: append_to_vector(v1, vr)...; with the same effect and discarding the return values, and I didn't have an answerAttila
@Attila My guess is allowing the ellipsis to be applied to something other than a parameter pack for expansion, without an intervening operator, would cause ambiguities in the grammar. I've always assumed fold expressions require parentheses around them for similar reasons.Hexaemeron

© 2022 - 2024 — McMap. All rights reserved.