使自定义范围v3视图可传递

时间:2018-07-18 11:24:06

标签: c++ range-v3

我正在尝试使用范围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()内的假定返回范围,我遍历了正确的小部件,但返回行中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

目前,我最好的猜测是我将protectstd::forward&&std::move弄乱了,尽管我试图尽量靠近到filter.hpp(因为我认为我已经相当了解),并且还尝试了一些随机添加/删除“&”号和转发的操作,但没有成功。

任何建议如何解决此问题? (理想情况下,说明发生了什么?)。

谢谢。

脚注:我现在不关心c ++ 11的兼容性。

编辑:

我把烂摊子推到了github

2 个答案:

答案 0 :(得分:4)

std::bind很奇怪。如果将bind_expression的调用结果传递给{{1},则传递给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)是一个range-v3实用程序,用于捕获protect对象内部的功能对象,如果这些功能对象恰好是bind,则可以防止std::bind陷入这种怪异状态。 s。对于非bind_expressionbind_expressions具有“按值捕获右值和按引用捕获左值”的行为(范围v3中的大多数情况都假定调用方保证左值的生存期,但右值可能在需要之前“消失”因此必须存储)。

不幸的是,您在“部分应用程序”重载中将protectprotect一起使用:

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)))); } 与range-v3的设计不同:它存储您在返回的std::bind中传递的所有内容的副本,并在调用时将表示这些存储对象的左值传递给包装函数。最终结果是,您的重载返回了一个bind_expression,它被包裹在bind_expression中,其中包含调用者传入的make_pipeable的副本。

当测试程序在另一个范围内“传递”时,vector会使用该范围和一个左值表示存储在绑定表达式中的make_pipeable副本来调用其他重载。您将该左值传递给vector,该左值(如上所述)假定其调用者将保证左值只要它产生的view::zip仍然有效。当然不是这样:zip_view临时文件(包括存储在其中包含的make_pipeable中的vector)在测试程序的range-for语句中评估了初始化程序后被销毁了。当range-for尝试访问该死的bind_expression时,会发生UB,这在这种情况下表现为空范围。

解决方法是不要在“部分应用程序”重载中使用vector,而是将范围的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来防止此错误,那就太好了。)

答案 1 :(得分:0)

经过更多尝试后,我们发现std::bind应该收到一个std::ref到掩码。

            return make_pipeable(
                std::bind(*this,
                          std::placeholders::_1,
                          std::ref(msk));

如果没有,那么-我的理解-masker的寿命将超过msk的临时副本。