Сделать пользовательский диапазон 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_expression
s. Для не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
,