copy_if with different types

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.


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;
}

Tags:

C++

C++14