使自定义范围 v3 视图可管道化
make custom range v3 view pipeable
我正在尝试使用范围 v3 实现屏蔽范围视图。不知何故,我最终陷入了我实现
ranges::view::masker(datarange, mask)
有效,但管道版本
ranges::view::all(datarange) | ranges::view::masker(mask)
没有,尽管在内部结构的operators
内,面具正确到达。(我将masker
的实现放入ranges::view
命名空间中,尽管它不是范围 v3 的一部分)。
我的测试程序相对琐碎,创建一些小部件和一个无意义的掩码
class Widget
{
private:
int m_int{0};
public:
Widget() {}
Widget( int i ) : m_int( i ) {}
int the_int() const { return m_int; }
};
inline std::ostream& operator<<( std::ostream& str, const Widget& obj )
{
str << 't' << obj.the_int();
return str;
}
int main()
{
std::vector<Widget> widgets;
std::vector<bool> mask;
for ( auto i : ranges::view::indices( 24 ) ) {
widgets.emplace_back( i );
mask.push_back( i % 3 != 1 );
}
std::cout << "wrapped" << std::endl;
for ( auto& el : ranges::view::masker( widgets, mask ) ) {
std::cout << el << std::endl;
}
std::cout << std::endl;
std::cout << std::endl;
std::cout << "piped" << std::endl;
for ( auto& el : ranges::view::all( widgets ) | ranges::view::masker( mask ) ) {
std::cout << el << std::endl;
}
return 0;
}
忽略命名空间并调试打印输出,masker
只是将数据范围和掩码压缩在一起,在掩码上过滤并将小部件作为视图返回:
struct mask_fn
{
template<typename Rng, typename Msk>
auto operator()(Rng&& rng, Msk&& msk) const
{
CONCEPT_ASSERT(Range<Rng>());
CONCEPT_ASSERT(Range<Msk>());
return ranges::view::zip(std::forward<Rng>(rng),
std::forward<Msk>(msk)) |
ranges::view::filter([](auto&& range_item) -> bool {
return range_item.second;
}) |
ranges::view::transform(
[](auto&& range_item) -> decltype(auto) {
return range_item.first;
});
}
template<typename Msk>
auto operator()(Msk&& msk) const -> decltype(
make_pipeable(std::bind(*this, std::placeholders::_1,
protect(std::forward<Msk>(msk)))))
{
CONCEPT_ASSERT(Range<Msk>());
return make_pipeable(
std::bind(*this,
std::placeholders::_1,
protect(std::forward<Msk>(msk))));
}
};
RANGES_INLINE_VARIABLE(mask_fn, masker)
上面的程序旨在打印出相同的结果范围两次,但我只得到:
wrapped
0
2
3
5
6
8
9
11
12
14
15
17
18
20
21
23
piped
因此,在使用auto operator()(Rng&& rng, Msk&& msk) const
正确的小部件循环时,带有auto operator()(Msk&& msk) const
的版本不会返回任何内容。
我尝试向前者添加一些调试打印输出(因为它最终被后者调用)并观察掩码是否正确到达。
struct mask_fn
{
template<typename Rng, typename Msk>
auto operator()(Rng&& rng, Msk&& msk) const
{
CONCEPT_ASSERT(Range<Rng>());
CONCEPT_ASSERT(Range<Msk>());
for(auto t :
ranges::view::zip(rng, msk) |
ranges::view::filter([](auto&& range_item) ->
bool {
return range_item.second;
}) |
ranges::view::transform(
[](auto&& range_item) -> decltype(auto) {
return range_item.first;
}))
std::cout << "w: " << t << std::endl;
return ranges::view::zip(std::forward<Rng>(rng),
std::forward<Msk>(msk)) |
ranges::view::filter([](auto&& range_item) -> bool {
std::cout << "checking widget "
<< range_item.first << std::endl;
std::cout << "returning " << range_item.second
<< std::endl;
return range_item.second;
}) |
ranges::view::transform(
[](auto&& range_item) -> decltype(auto) {
return range_item.first;
});
}
template<typename Msk>
auto operator()(Msk&& msk) const -> decltype(
make_pipeable(std::bind(*this, std::placeholders::_1,
protect(std::forward<Msk>(msk)))))
{
CONCEPT_ASSERT(Range<Msk>());
return make_pipeable(
std::bind(*this,
std::placeholders::_1,
protect(std::forward<Msk>(msk))));
}
};
RANGES_INLINE_VARIABLE(mask_fn, masker)
(稍微削减输出)可以看到,在operator()
I 循环访问正确的小部件中使用假定的返回范围,但返回行中 lambda 内部的打印输出显示所有项目的"假"标志。
wrapped
w: 0
w: 2
w: 3
w: 5
<snap>
w: 20
w: 21
w: 23
checking widget 0
returning 1
0
checking widget 1
returning 0
checking widget 2
returning 1
2
checking widget 3
returning 1
3
<snap>
checking widget 22
returning 0
checking widget 23
returning 1
23
piped
w: 0
w: 2
w: 3
w: 5
<snap>
w: 20
w: 21
w: 23
checking widget 0
returning 0
checking widget 1
returning 0
checking widget 2
returning 0
checking widget 3
returning 0
<snap>
checking widget 22
returning 0
checking widget 23
我目前最好的猜测是我把protect
、std::forward
、&&
或std::move
弄乱了,尽管我试图尽可能接近filter.hpp
(因为我认为我已经很好地理解了它),并且还尝试了一些随机添加/删除与号和转发,但没有成功。
任何建议如何解决这个问题?(理想情况下解释正在发生的事情,一起?
提前谢谢。
脚注:我目前不关心 c++11 兼容性。
编辑:
我把烂摊子推到了github上。
std::bind
很奇怪。如果将bind_expression
(调用std::bind
的结果)传递给std::bind
,则会生成一个从"叶子"向下计算的表达式树。例如:
auto f = [](int i, int j){ return i * j; };
auto g = [](int i) { return i + 1; };
auto b = std::bind(f, std::bind(g, std::placeholders::_1), std::placeholders::_2);
std::cout << b(0, 3) << 'n'; // prints 3
此处b(0, 3)
的调用等效于f(g(0), 3)
。
protect
是一个 range-v3 实用程序,用于捕获bind
对象中的函数对象,如果这些函数对象恰好是bind_expression
,它可以防止std::bind
参与这种怪异。对于非bind_expressions
,protect
具有"按值捕获左值和按引用捕获左值"行为(range-v3 中的大多数内容都假定调用方保证左值的生存期,但右值可能会在需要时"消失",因此必须存储)。
不幸的是,您在"部分应用程序"重载中使用带有Range
的protect
:
template<typename Msk>
auto operator()(Msk&& msk) const -> decltype(
make_pipeable(std::bind(*this, std::placeholders::_1,
protect(std::forward<Msk>(msk)))))
{
CONCEPT_ASSERT(Range<Msk>());
return make_pipeable(
std::bind(*this,
std::placeholders::_1,
protect(std::forward<Msk>(msk))));
}
std::bind
的设计与 range-v3 不同:它存储您在返回的bind_expression
中传递的任何内容的副本,并在调用时将表示这些存储对象的 lvalues 传递给包装的函数。最终效果是,重载返回一个包装在make_pipeable
中的bind_expression
,该包含调用方传入的vector
的副本。
当测试程序在另一个范围内"管道"时,make_pipeable
会使用该范围调用您的其他重载,并用一个 lvalue 表示存储在绑定表达式中的vector
副本。您将该 lvalue 传递给view::zip
,后者(如上所述)假定其调用方将保证只要它产生的zip_view
,左值将保持活动状态。当然不是这样:临时make_pipeable
(包括存储在它所包含的bind_expression
中的vector
)在测试程序中的 range-for 语句中计算初始值设定项后被销毁。当 range-for 尝试访问vector
时,会发生 UB,在这种情况下表现为空范围。
解决方法是不在"部分应用程序"重载中使用protect
,而是将范围的all_view
传递给std::bind
:
template<typename Msk>
auto operator()(Msk&& msk) const -> decltype(
make_pipeable(std::bind(*this, std::placeholders::_1,
ranges::view::all(std::forward<Msk>(msk)))))
{
CONCEPT_ASSERT(Range<Msk>());
return make_pipeable(
std::bind(*this,
std::placeholders::_1,
ranges::view::all(std::forward<Msk>(msk))));
}
(诚然,如果protect
通过拒绝接受Range
来防止此错误,那就太好了。
经过更多的尝试,我们发现std::bind
应该收到面具的std::ref
。
return make_pipeable(
std::bind(*this,
std::placeholders::_1,
std::ref(msk));
如果没有,那么 - 所以我的理解 -masker
将比msk
的临时副本更长久.
- 如何在选项卡视图Qt中设置一个新项目,并保存以前的项目
- 在进程中对同一管道进行读取和写入时C++管道出现问题
- 视图中的参数推导失败:take_while
- IPC使用多个管道和分支进程来运行Python程序
- 如何维护资源管理器项目视图中当前可见的项目列表
- 在另一个类视图中添加最多2个图表的正确方法是什么
- 如何创建函数管道,以便函数一个接一个地运行?
- Gstreamer 管道从命令 lne 到 c 代码
- OpenVR:向视图方向移动
- 使用 WIN32 API (C/C++) 对特定树视图项进行着色
- 将所选值(通过视图)从 boost::multi_array 复制到另一个数组 (C++)
- 外壳包装器句柄/执行交互式命令管道C++ UNIX
- 如何在不使用滚动条的情况下使视图更改
- 将旧管道转换为现代 openGL 时出现问题
- 列表视图更改选择颜色
- Qt - QVector 和模型视图 - 从列表视图获取自定义类的最佳方法是什么?
- 如何使用管道在父级和子级之间来回传递文件
- 在没有管理员权限的情况下连接到同一网络中的命名管道
- 如何测量管道延迟?
- 使自定义范围 v3 视图可管道化