I have a const std::vector<T> source
of a big size, and a std::vector<T> dest
.
I want to apply a transformation to each element in source
and store it in dest
; all of this in parallel, and knowing that T
is not default constructible.
What I initially attempted was using std::transform
with a parallel execution policy:
std::transform(std::execution::par_unseq,
source.begin(), source.end(),
dest.begin(),
[](const T& elem) { return op(elem); }
);
However, when I first compiled and run this, to my surprise, I discovered that, although the transform
"loops" for source.size()
times, dest
's content remains unchanged.
I discovered that this is because dest
must have the same size of source
prior to the transform
.
However, I cannot do resize dest
to the size of source
, because T
is not default constructible as it does not have a default constructor. I also would prefer not to provide a default constructor for it (first of all, it does not make sense in T
's logic, but you can think that it would be expensive to call).
Does C STL offer any other algorithm for achieving what I have in mind?
What would suffice is an algorithm where each thread computes its own part of the source
vector, and then the results are collected and joined into the same dest
vector.
CodePudding user response:
Try using a std::vector<>
of std::optional<T>
:
#include <algorithm>
#include <execution>
#include <optional>
#include <vector>
struct T { // NOTE: T is NOT default-constructible!
T(int x) : value(x) {}
operator int() { return value; }
int value;
};
T op(const T& in) {
return in.value * 2;
}
#include <iostream>
int main() {
const std::vector<T> source = {4, 5, 6, 7, 8, 9, 10, 11};
std::vector<std::optional<T>> dest(source.size());
std::transform(
std::execution::par_unseq,
source.begin(), source.end(),
dest.begin(),
[](const T& elem) { return op(elem); }
);
for (auto i : dest) {
std::cout << *i << " ";
}
std::cout << std::endl;
}
Sample output: 8 10 12 14 16 18 20 22
Godbolt demo: https://godbolt.org/z/ecf95cneq
This is admittedly not that idiomatic, as your dest
array now has every element wrapped up in an std::optional<>
container, but I believe it otherwise offers the semantics you specify.
CodePudding user response:
Inspired by @saxbophone answer, you could also use a vector of pointers.
Credit for the code template to @saxbophone.
#include <algorithm>
#include <execution>
#include <optional>
#include <vector>
struct T { // NOTE: T is NOT default-constructible!
T(int x) : value(x) {}
operator int() { return value; }
int value;
};
T* op(const T& in) {
return new T(in.value*2);
}
#include <iostream>
int main() {
const std::vector<T> source = {4, 5, 6, 7, 8, 9, 10, 11};
std::vector<T*> dest(source.size());
std::transform(
std::execution::par_unseq,
source.begin(), source.end(),
dest.begin(),
[](const T& elem) { return op(elem); }
);
for (auto i : dest) {
std::cout << *i << " ";
}
std::cout << std::endl;
// Don't forget to delete dest.
}
Live example: https://godbolt.org/z/r73qr1ze3