copy_if具有不同类型

时间:2018-06-18 17:23:45

标签: c++ c++14

如果我知道如何提取匹配类型,是否存在表达从不同类型的源容器有条件地复制到目标容器的意图的现代方法?

将问题作为代码示例提出更容易:

#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

6 个答案:

答案 0 :(得分:10)

使用range-v3

std::vector<Foo> get_valid_foos(const std::vector<FooBar>& foobars) {
    return foobars
        | view::filter(&FooBar::is_valid)
        | view::transform(&FooBar::foo);
}

那非常富有表现力。

答案 1 :(得分:4)

与提出的其他答案一样,Ranges为这个问题提供了一个非常简洁的解决方案。我们距离C ++ 20还有几年的标准化(在企业环境中可以访问之前的几年),所以我们需要一个兼容C ++ 17的解决方案。

您正在寻找的是假设的transform_if,它未包含在various reasons的标准库中

你有几个选择。

最简单的方法是合并std::copy_ifstd::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;
}

这种方法的缺点是它会为每个要转换的对象创建临时FooBar个对象,这可能是您不希望看到的。您可以推出自己的transform_if算法实现:

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

然后您可以直接在代码中使用

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

答案 2 :(得分:2)

虽然不如range-v3好,但您可以使用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

答案 3 :(得分:1)

反向插入器迭代器it将尝试并push_back分配给it的任何对象。 当前,由于it = foobar格式错误,您会收到错误消息。确实vector_of_foo.push_back(foobar)本身格式不正确。

如果只有一种方法可以隐式FooBar转换为Foo ...等等!有!好吧,令人讨厌的事情是它在FooFooBar之间引入了循环依赖。让我们用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>;

现在,std::back_inserter(foos) = FooBar{}完成了预期的工作。并且copy_if也会表现出来!

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

演示:http://coliru.stacked-crooked.com/a/a40aeca7a9a057b2

答案 4 :(得分:0)

这需要假设的 this.authorService.getAuthors() .subscribe((authors: IAuthor[]) => { this.authors = authors, authors.forEach(function (author) { this.authorDropdown.options .push({ key: this.author.authorId, value: this.author.authorName }); }); }); ,这是不可用的(why?)。

稍微昂贵的解决方法是将std::transform_if转换为临时向量,然后是std::copy_if

std::transform

Demo.

答案 5 :(得分:0)

#include <iterator>
#include <functional>
#include <vector>
#include <iostream>

template<typename Container,
         typename In>
class MappedInsertIterator
    : public std::back_insert_iterator<Container>
{
protected:
    using Out = typename Container::value_type;
    using Transformer = std::function<Out(const In&)>;

public:
    MappedInsertIterator() = delete;

    template<typename F>
        requires std::is_invocable_r_v<Out, F, In>
    explicit MappedInsertIterator(Container& c, F&& fn);

    virtual ~MappedInsertIterator() = default;

public:
    auto operator*() -> MappedInsertIterator&;
    auto operator=(const auto& value) -> MappedInsertIterator&;

protected:
    Transformer m_fn;
};

template<typename Container, typename In>
template<typename F>
    requires std::is_invocable_r_v<typename Container::value_type, F, In>
inline MappedInsertIterator<Container, In>::MappedInsertIterator(Container& c, F&& fn)
    : std::back_insert_iterator<Container>(c)
    , m_fn(std::forward<F>(fn))
{}

template<typename Container, typename In>
inline auto MappedInsertIterator<Container, In>::operator*() -> MappedInsertIterator&
{ return *this; }

template<typename Container, typename In>
auto MappedInsertIterator<Container, In>::operator=(const auto& value) -> MappedInsertIterator&
{
    std::back_insert_iterator<Container>::operator=(m_fn(value));
    return *this;
}


int main()
{
    struct Telemetry { float voltage; unsigned timestamp; };

    std::vector<Telemetry> items =
        {
            Telemetry { .voltage = 200, .timestamp = 101 }, // accepted
            Telemetry { .voltage = 250, .timestamp = 102 }, // accepted
            Telemetry { .voltage = 300, .timestamp = 203 }, // rejected
        };

    static auto predicate = [](const Telemetry& t){ return t.timestamp < 200; };
    static auto transform = [](const Telemetry& t){ return t.voltage; };

    std::vector<float> voltages;
    using iterator_t = MappedInsertIterator<decltype(voltages), Telemetry>;
    std::copy_if(items.cbegin(), items.cend(), iterator_t(voltages, transform), predicate);

    for (const auto& v : voltages)
        std::cout << v << std::endl;
}