Is there a modern way of expressing the intent to conditionally copy from a source container of a different type to a destination container if I know how to extract the matching type?
It is easier to pose the question as a code-example:
#include <algorithm>
#include <vector>
struct Foo {};
struct FooBar{
bool is_valid;
Foo foo;
};
std::vector<Foo> get_valid_foos(const std::vector<FooBar>& foobars){
std::vector<Foo> valid_foos;
for(const auto& fbar : foobars){
if(fbar.is_valid)
valid_foos.push_back(fbar.foo);
}
return valid_foos;
}
std::vector<Foo> get_valid_foos_modern(const std::vector<FooBar>& foobars){
std::vector<Foo> valid_foos;
std::copy_if(foobars.begin(), foobars.end(), std::back_inserter(valid_foos),
[](const auto& foobar){
return foobar.is_valid;
});
//?? std::copy requires input and output types to match
return valid_foos;
}
https://godbolt.org/g/miPbfW
Returns an iterator pointing to the element that follows the last element written in the result sequence.
std::copy. Copies the elements in the range [first,last) into the range beginning at result . The function returns an iterator to the end of the destination range (which points to the element following the last element copied).
Like the other answer put forth, Ranges offer a very concise solution to this problem. We're still a few years out from C++20 being standardized though (and another few years before it becomes accessible in enterprise environments) so we need a C++17-compatible solution.
What you're looking for is a hypothetical transform_if
, which was not included in the Standard Library for various reasons
You have a couple of options.
The simplest is to just combine std::copy_if
and std::transform
:
std::vector<Foo> get_valid_foos_modern(const std::vector<FooBar>& foobars){
std::vector<FooBar> valid_foobars;
std::copy_if(foobars.begin(), foobars.end(), std::back_inserter(valid_foobars), [](const auto& foobar){
return foobar.is_valid;
});
std::vector<Foo> valid_foos;
std::transform(valid_foobars.begin(), valid_foobars.end(), std::back_inserter(valid_foos), [](auto const& fooBar) {return fooBar.foo;});
return valid_foos;
}
The downside to this approach is that it creates temporary FooBar
objects for each object that is going to get transformed, which you may find undesirable. You could roll your own transform_if
algorithm implementation:
template<typename InputIterator, typename OutputIterator, typename Predicate, typename TransformFunc>
OutputIterator transform_if(
InputIterator&& begin,
InputIterator&& end,
OutputIterator&& out,
Predicate&& predicate,
TransformFunc&& transformer
) {
for(; begin != end; ++begin, ++out) {
if(predicate(*begin))
*out = transformer(*begin);
}
return out;
}
Which you'd then be able to use directly in your code:
std::vector<Foo> get_valid_foos_modern(const std::vector<FooBar>& foobars){
std::vector<Foo> valid_foos;
transform_if(
foobars.begin(),
foobars.end(),
std::back_inserter(valid_foos),
[](const auto& foobar) { return foobar.is_valid;},
[](auto const& foobar) { return foobar.foo;}
);
return valid_foos;
}
Using range-v3:
std::vector<Foo> get_valid_foos(const std::vector<FooBar>& foobars) {
return foobars
| view::filter(&FooBar::is_valid)
| view::transform(&FooBar::foo);
}
That's pretty expressive.
Although not as nice as range-v3, you could use Boost Range:
std::vector<Foo> get_valid_foos(const std::vector<FooBar>& foobars) {
std::vector<Foo> result;
boost::push_back(
result, foobars | boost::adaptors::filtered([](const FooBar& foobar) {
return foobar.is_valid;
}) | boost::adaptors::transformed([](const FooBar& foobar) {
return foobar.foo;
}));
return result;
}
Demo
A back insertor iterator it
will try and push_back
anything that is assigned to it
.
Currently, you get an error because it = foobar
is ill-formed. Indeed vector_of_foo.push_back(foobar)
is ill-formed itself.
If only there was a way to implicitly convert a FooBar
into a Foo
... wait! There is! Well, the annoying thing is that it introduces a circular dependency between Foo
and FooBar
. Let us break it with CRTP!
template<class TFoo>
struct TFooBar
{
bool is_valid;
TFoo foo;
};
struct Foo
{
Foo() = default;
Foo(TFooBar<Foo> const& src) { *this = src.foo; }
};
using FooBar = TFooBar<Foo>;
Now, std::back_inserter(foos) = FooBar{}
does what is expected. And copy_if
will behave too!
auto get_valid_foos_modern(const std::vector<FooBar>& foobars){
std::vector<Foo> result;
std::copy_if(begin(foobars), end(foobars), std::back_inserter(result),
[](const auto& foobar) {
return foobar.is_valid;
});
return result;
}
Demo: http://coliru.stacked-crooked.com/a/a40aeca7a9a057b2
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With