Сделать пользовательский диапазон 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() Я перебираю правильные виджеты, но распечатка из лямбд в обратной строке показывает "ложные" флаги для всех элементов.

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.

2 ответа

Решение

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 объект, который мешает std::bind от привлечения этой странности, если эти функциональные объекты оказываются bind_expressions. Для неbind_expressions, protect имеет поведение "захватывать значения по значению и значения по значению по ссылке" (большинство вещей в range-v3 предполагают, что вызывающая сторона гарантирует время жизни значений l, но что значения могут "исчезнуть" раньше, чем это необходимо, и поэтому должны быть сохранены).

К сожалению, вы используете 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 и передает lvalues, обозначающие эти сохраненные объекты, в упакованную функцию при вызове. Конечным эффектом является то, что ваша перегрузка возвращает bind_expression завернутый в make_pipeable который содержит копию vector звонящий прошел.

Когда программа испытаний "трубы" в другом диапазоне, make_pipeable вызывает вашу другую перегрузку с этим диапазоном и lvalue, обозначающим vector копия хранится внутри выражения связывания. Вы передаете это значение view::zip который (как описано выше) предполагает, что его вызывающая сторона гарантирует, что lvalue останется живым до тех пор, пока zip_view это производит. Это, конечно, не тот случай: make_pipeable временно - в том числе vector хранится внутри bind_expression он содержит - уничтожается после оценки инициализатора в операторе 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,

Другие вопросы по тегам