如果我知道如何提取匹配类型,是否存在表达从不同类型的源容器有条件地复制到目标容器的意图的现代方法?
将问题作为代码示例提出更容易:
#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;
}
答案 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_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
个对象,这可能是您不希望看到的。您可以推出自己的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;
}
答案 3 :(得分:1)
反向插入器迭代器it
将尝试并push_back
分配给it
的任何对象。
当前,由于it = foobar
格式错误,您会收到错误消息。确实vector_of_foo.push_back(foobar)
本身格式不正确。
如果只有一种方法可以隐式将FooBar
转换为Foo
...等等!有!好吧,令人讨厌的事情是它在Foo
和FooBar
之间引入了循环依赖。让我们用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;
}
答案 4 :(得分:0)
这需要假设的 ,这是不可用的(why?)。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 });
});
});
稍微昂贵的解决方法是将std::transform_if
转换为临时向量,然后是std::copy_if
:
std::transform
答案 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;
}