While learning about template parameter packs, I'm trying to write a clever, simple function to efficiently append two or more std::vector
containers together.
Below are two initial solutions.
Version 1 is elegant but buggy, as it relies on side-effects during the expansion of the parameter pack, and the order of evaluation is undefined.
Version 2 works, but relies on a helper function that requires two cases. Yuck.
Can you see if you can come up with a simpler solution? (For efficiency, the vector data should not be copied more than once.)
#include <vector>
#include <iostream>
// Append all elements of v2 to the end of v1.
template<typename T>
void append_to_vector(std::vector<T>& v1, const std::vector<T>& v2) {
for (auto& e : v2) v1.push_back(e);
}
// Expand a template parameter pack for side effects.
template<typename... A> void ignore_all(const A&...) { }
// Version 1: Concatenate two or more std::vector<> containers into one.
// Nicely simple, but buggy as the order of evaluation is undefined.
template<typename T, typename... A>
std::vector<T> concat1(std::vector<T> v1, const A&... vr) {
// Function append_to_vector() returns void, so I enclose it in (..., 1).
ignore_all((append_to_vector(v1, vr), 1)...);
// In fact, the evaluation order is right-to-left in gcc and MSVC.
return v1;
}
// Version 2:
// It works but looks ugly.
template<typename T, typename... A>
void concat2_aux(std::vector<T>& v1, const std::vector<T>& v2) {
append_to_vector(v1, v2);
}
template<typename T, typename... A>
void concat2_aux(std::vector<T>& v1, const std::vector<T>& v2, const A&... vr) {
append_to_vector(v1, v2);
concat2_aux(v1, vr...);
}
template<typename T, typename... A>
std::vector<T> concat2(std::vector<T> v1, const A&... vr) {
concat2_aux(v1, vr...);
return v1;
}
int main() {
const std::vector<int> v1 { 1, 2, 3 };
const std::vector<int> v2 { 4 };
const std::vector<int> v3 { 5, 6 };
for (int i : concat1(v1, v2, v3)) std::cerr << " " << i;
std::cerr << "\n"; // gcc output is: 1 2 3 5 6 4
for (int i : concat2(v1, v2, v3)) std::cerr << " " << i;
std::cerr << "\n"; // gcc output is: 1 2 3 4 5 6
}
There is a related answer on concatenation of strings: https://stackoverflow.com/a/21806609/1190077 . Adapted here, it looks like:
This seems to work!
However, is the evaluation order of the template parameter pack now well-defined, or is it by accident that the compiler did the right thing?
The answer by Yakk (https://stackoverflow.com/a/23439527/1190077) works well.
Here is a polished version, incorporating my improvement to
do_in_order
and removing thesum_size
external function:A helper type: I dislike using
int
for it.Add up sizes:'
Concat. Returns type to be ignored:
Micro optimization, and lets you concat vectors of move only types:
actual function. Above stuff should be in a details namespace:
apologies for any typos.