代码之家  ›  专栏  ›  技术社区  ›  arynaq

使用不同类型复制

  •  10
  • arynaq  · 技术社区  · 7 年前

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

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

    #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

    5 回复  |  直到 7 年前
        1
  •  11
  •   Barry    7 年前

    使用 range-v3 :

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

    很有表现力。

        2
  •  4
  •   Xirema    7 年前

    与提出的另一个答案一样,ranges为这个问题提供了一个非常简洁的解决方案。不过,我们离c++20标准化还有几年的时间(在企业环境中实现标准化还有几年的时间),所以我们需要一个c++17兼容的解决方案。

    你要找的是一个假设 transform_if ,它不包含在 various reasons

    你有两个选择。

    最简单的方法就是 std::copy_if 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;
    }
    

    这种方法的缺点是它创建临时的 FooBar 将要转换的每个对象的对象,您可能会发现这些对象不受欢迎。你可以自己滚 变换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;
    }
    
        3
  •  2
  •   Justin    7 年前

    虽然没有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

        4
  •  1
  •   YSC    7 年前

    反向插入器迭代器 it 将尝试和 push_back 任何分配给 . 目前,您会收到一个错误,因为 it = foobar 是病态的。的确 vector_of_foo.push_back(foobar) 是病态的。

    如果有办法 隐含地 转换为 FooBar 变成一个 Foo …等待!有!令人恼火的是它引入了 福巴 . 让我们用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

        5
  •  0
  •   Sergey Kalinichenko    7 年前

    这需要一个假设 std::transform_if ,但不可用( why? )

    有点贵的工作是 std::copy_if 变成一个临时向量,然后是 std::transform :

    std::vector<FooBar> foobars_with_valid_foos;
    std::copy_if(
        foobars.begin()
    ,   foobars.end()
    ,   std::back_inserter(foobars_with_valid_foos)
    ,   [](const auto& foobar){
            return foobar.is_valid;
        }
    );
    std::vector<Foo> valid_foos;
    std::transform(
        foobars_with_valid_foos.begin()
    ,   foobars_with_valid_foos.end()
    ,   std::back_inserter(valid_foos)
    ,   [](const auto& foobar){
            return foobar.foo;
        }
    );
    return valid_foos;
    

    Demo.

    推荐文章