【问题标题】:make custom range v3 view pipeable使自定义范围 v3 视图可管道化
【发布时间】:2018-07-18 11:24:05
【问题描述】:

我正在尝试使用范围 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&amp;&amp; rng, Msk&amp;&amp; msk) const 时,正确的小部件会循环,auto operator()(Msk&amp;&amp; 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() 内的假定返回范围我循环了正确的小部件,但返回行中的 lambdas 内的打印输出显示所有项目的“假”标志。

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&amp;&amp;std::move,尽管我试图尽可能地贴近filter.hpp(因为我认为我已经很好地理解了它)并且还尝试了一些随机添加/删除&符号和转发但没有成功。

有什么建议可以解决这个问题吗? (理想情况下,还要解释发生了什么?)。

提前致谢。

脚注:我目前不关心 c++11 的兼容性。

编辑:

我把这个烂摊子推到github

【问题讨论】:

    标签: c++ range-v3


    【解决方案1】:

    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_expressions,则可以防止 std::bind 参与这种奇怪的事情。对于非bind_expressionsprotect 具有“按值捕获右值和按引用捕获左值”行为(range-v3 中的大多数事物假定调用者保证左值的生命周期,但右值可能在需要之前“消失”,因此必须存储)。

    不幸的是,您在“部分应用程序”重载中使用了 protectRange

    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 中传递的任何内容的副本,并在调用时将表示这些存储对象的左值传递给包装函数。最终效果是您的重载返回一个 bind_expression 包裹在一个 make_pipeable 中,该 make_pipeable 包含调用者传入的 vector 的副本。

    当测试程序在另一个范围内“管道”时,make_pipeable 调用您的另一个重载,该范围和一个左值表示存储在绑定表达式中的vector 副本。您将该左值传递给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 能够通过拒绝接受Ranges 来防范此错误,那就太好了。)

    【讨论】:

      【解决方案2】:

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

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

      如果不是,那么 - 所以我的理解 - masker 将比 msk 的临时副本寿命更长。

      【讨论】:

      • 仅供参考,当有人将右值 View 传递给您的“部分应用程序”重载时,这将不起作用:您将返回一个存储 reference_wrapperbind_expression 到那个(可能是临时的) ) View 对象。
      猜你喜欢
      • 1970-01-01
      • 2021-11-26
      • 2019-10-04
      • 1970-01-01
      • 2012-01-13
      • 2021-05-08
      • 2022-08-09
      • 1970-01-01
      • 2020-01-07
      相关资源
      最近更新 更多