使自定义范围 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
只是将数据范围和掩码压缩在一起,过滤掩码和 returns 小部件作为视图:
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
的版本不会 return 任何东西。
我尝试向前者添加一些调试打印输出(因为它最终被后者调用)并观察到掩码正确到达。
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()
内使用假定的 return 范围,我循环遍历了正确的小部件,但是 [=62] 中的 lambda 中的打印输出=] 行显示所有项目的 "false" 标志。
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
(因为我认为我已经相当理解它了)并且还尝试了一些随机的 adding/removing 符号和转发但没有成功。
有什么解决方法的建议吗? (最好能解释发生了什么?)。
提前致谢。
脚注:我目前不关心 c++11 兼容性。
编辑:
我把烂摊子推到了github。
经过更多的尝试,我们发现 std::bind
应该接收到掩码的 std::ref
。
return make_pipeable(
std::bind(*this,
std::placeholders::_1,
std::ref(msk));
如果不是,那么 - 我的理解 - masker
的寿命将超过 msk
.
的临时副本
std::bind
很奇怪。如果将 bind_expression
- 调用 std::bind
的结果 - 传递给 std::bind
,它会生成一个从 "leaves" 向下求值的表达式树。例如:
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
对象内的函数对象,如果这些函数对象恰好是 [=14],则可以防止 std::bind
发生这种怪事=]秒。对于非 bind_expressions
,protect
具有 "capture rvalues by value and lvalues by reference" 行为(range-v3 中的大多数事情都假设调用者保证左值的生命周期,但右值可能 "disappear" 在需要之前,因此必须被存储)。
不幸的是,您在 "partial application" 重载中使用 protect
和 Range
:
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
中传递的任何内容的副本,并在调用时将表示这些存储对象的左值传递给包装函数。最终效果是您的重载 returns 一个 bind_expression
包裹在一个 make_pipeable
中,该 make_pipeable
包含调用方传入的 vector
的副本。
当测试程序 "pipes" 在另一个范围内时,make_pipeable
使用该范围调用您的其他重载,左值表示存储在绑定表达式中的 vector
副本。您将该左值传递给 view::zip
,后者(如上所述)假定其调用者将保证只要它产生 zip_view
左值就会保持活动状态。这当然不是这种情况: make_pipeable
临时 - 包括存储在它包含的 bind_expression
中的 vector
- 在测试程序中的 range-for 语句中评估初始化程序后被销毁.当 range-for 尝试访问死区 vector
时,出现 UB,在这种情况下表现为空范围。
解决方法是不在 "partial application" 重载中使用 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
来防止此错误。)
我正在尝试使用范围 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
只是将数据范围和掩码压缩在一起,过滤掩码和 returns 小部件作为视图:
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
的版本不会 return 任何东西。
我尝试向前者添加一些调试打印输出(因为它最终被后者调用)并观察到掩码正确到达。
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()
内使用假定的 return 范围,我循环遍历了正确的小部件,但是 [=62] 中的 lambda 中的打印输出=] 行显示所有项目的 "false" 标志。
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
(因为我认为我已经相当理解它了)并且还尝试了一些随机的 adding/removing 符号和转发但没有成功。
有什么解决方法的建议吗? (最好能解释发生了什么?)。
提前致谢。
脚注:我目前不关心 c++11 兼容性。
编辑:
我把烂摊子推到了github。
经过更多的尝试,我们发现 std::bind
应该接收到掩码的 std::ref
。
return make_pipeable(
std::bind(*this,
std::placeholders::_1,
std::ref(msk));
如果不是,那么 - 我的理解 - masker
的寿命将超过 msk
.
std::bind
很奇怪。如果将 bind_expression
- 调用 std::bind
的结果 - 传递给 std::bind
,它会生成一个从 "leaves" 向下求值的表达式树。例如:
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
对象内的函数对象,如果这些函数对象恰好是 [=14],则可以防止 std::bind
发生这种怪事=]秒。对于非 bind_expressions
,protect
具有 "capture rvalues by value and lvalues by reference" 行为(range-v3 中的大多数事情都假设调用者保证左值的生命周期,但右值可能 "disappear" 在需要之前,因此必须被存储)。
不幸的是,您在 "partial application" 重载中使用 protect
和 Range
:
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
中传递的任何内容的副本,并在调用时将表示这些存储对象的左值传递给包装函数。最终效果是您的重载 returns 一个 bind_expression
包裹在一个 make_pipeable
中,该 make_pipeable
包含调用方传入的 vector
的副本。
当测试程序 "pipes" 在另一个范围内时,make_pipeable
使用该范围调用您的其他重载,左值表示存储在绑定表达式中的 vector
副本。您将该左值传递给 view::zip
,后者(如上所述)假定其调用者将保证只要它产生 zip_view
左值就会保持活动状态。这当然不是这种情况: make_pipeable
临时 - 包括存储在它包含的 bind_expression
中的 vector
- 在测试程序中的 range-for 语句中评估初始化程序后被销毁.当 range-for 尝试访问死区 vector
时,出现 UB,在这种情况下表现为空范围。
解决方法是不在 "partial application" 重载中使用 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
来防止此错误。)