임시 컨테이너를 사용하는 범위 파이프 라인을 작성하려면 어떻게해야합니까?
이 서명을 사용하는 타사 기능이 있습니다.
std::vector<T> f(T t);
또한 named 의 기존 잠재적으로 무한 범위 ( range-v3 정렬 )가 있습니다. 해당 범위의 모든 요소에 매핑 하고 모든 벡터를 모든 요소가있는 단일 범위로 평면화 하는 파이프 라인을 만들고 싶습니다 .T
src
f
본능적으로 다음과 같이 쓸 것입니다.
auto rng = src | view::transform(f) | view::join;
그러나 이것은 임시 컨테이너의 뷰를 만들 수 없기 때문에 작동하지 않습니다.
range-v3는 이러한 범위 파이프 라인을 어떻게 지원합니까?
나는 그것이 할 수 없다고 생각합니다. 의 없음 view
의 어디를 저장 한 임시 어떤 기계가 없다 -로부터 뷰의 개념에 대해 명시 적으로의 문서를 :
뷰는 변형이나 복사없이 사용자 지정 방식으로 기본 요소 시퀀스의 뷰를 제공하는 경량 래퍼입니다. 뷰는 생성 및 복사 비용이 저렴하며 소유하지 않는 참조 의미 체계가 있습니다.
그래서 그것이 join
작동하고 표현보다 오래 살기 위해서는 어딘가에 그 일시적인 것을 붙잡아 야합니다. 그 무언가는 action
. 이것은 작동합니다 ( 데모 ) :
auto rng = src | view::transform(f) | action::join;
분명히 src
무한이 아니라는 점을 제외하고 는 유한 한 경우에도 src
어쨌든 사용하고 싶은 오버 헤드가 너무 많습니다.
lvalue 컨테이너를 요구하는 대신 (그리고 여기 에 반복자 쌍을 반환하는) view::join
미묘하게 수정 된 버전 view::all
( 여기에 필요 )을 사용하기 위해 복사 / 다시 작성 해야 할 것입니다. 저장된 버전에 대한 반복기 쌍). 그러나 그것은 코드를 복사하는 데 수백 줄의 가치가 있으므로 작동하더라도 꽤 불만족스러워 보입니다.
range-v3는 댕글 링 반복자의 생성을 방지하기 위해 임시 컨테이너에 대한 뷰를 금지합니다. 이 예제는 뷰 컴포지션에서이 규칙이 필요한 이유를 정확하게 보여줍니다.
auto rng = src | view::transform(f) | view::join;
에서 반환 된 임시 벡터 view::join
의 begin
및 end
반복자 를 저장하면 f
사용되기 전에 무효화됩니다.
"모두 훌륭합니다. Casey.하지만 range-v3 뷰가 내부적으로 이와 같은 임시 범위를 저장하지 않는 이유는 무엇입니까?"
성능 때문입니다. 반복기 작업이 O (1)라는 요구 사항에 따라 STL 알고리즘의 성능이 예측되는 방식과 마찬가지로 뷰 구성의 성능은 뷰 작업이 O (1)라는 요구 사항에 따라 결정됩니다. 뷰가 "뒤에있는"내부 컨테이너에 임시 범위를 저장하는 경우 뷰 작업의 복잡성과 이에 따른 구성은 예측할 수 없게됩니다.
"좋아요.이 멋진 디자인을 모두 이해 했으니 어떻게하면이 작업을 할 수 있을까요?! ??"
뷰 컴포지션은 임시 범위를 저장하지 않기 때문에 다음과 같은 저장소에 직접 덤프해야합니다.
#include <iostream>
#include <vector>
#include <range/v3/range_for.hpp>
#include <range/v3/utility/functional.hpp>
#include <range/v3/view/iota.hpp>
#include <range/v3/view/join.hpp>
#include <range/v3/view/transform.hpp>
using T = int;
std::vector<T> f(T t) { return std::vector<T>(2, t); }
int main() {
std::vector<T> buffer;
auto store = [&buffer](std::vector<T> data) -> std::vector<T>& {
return buffer = std::move(data);
};
auto rng = ranges::view::ints
| ranges::view::transform(ranges::compose(store, f))
| ranges::view::join;
unsigned count = 0;
RANGES_FOR(auto&& i, rng) {
if (count) std::cout << ' ';
else std::cout << '\n';
count = (count + 1) % 8;
std::cout << i << ',';
}
}
이 접근 방식의 정확성은 view::join
입력 범위이므로 단일 패스 라는 사실에 따라 달라집니다 .
"초보자에게 친숙하지 않습니다. 전문가에게 친숙하지 않습니다. 범위 v3에서 '임시 저장 구체화 ™'에 대한 지원이없는 이유는 무엇입니까?"
우리가 그것에 대해 다루지 않았기 때문에-패치 환영;)
수정 됨
분명히 아래 코드는 뷰가 참조하는 데이터를 소유 할 수 없다는 규칙을 위반합니다. (하지만 이런 식으로 쓰는 것이 엄격히 금지되어 있는지 모르겠습니다.)
ranges::view_facade
사용자 지정보기를 만드는 데 사용 합니다. f
(한 번에 하나씩) 반환 된 벡터 를 포함하여 범위로 변경합니다. 이를 통해 view::join
이러한 범위의 범위 에서 사용할 수 있습니다 . 확실히 우리는 요소에 무작위 또는 양방향 액세스를 할 수 없으며 (하지만 view::join
그 자체로 범위를 입력 범위로 저하시킵니다) 할당 할 수도 없습니다.
struct MyRange
Eric Niebler의 저장소 에서 복사 하여 약간 수정했습니다.
#include <iostream>
#include <range/v3/all.hpp>
using namespace ranges;
std::vector<int> f(int i) {
return std::vector<int>(static_cast<size_t>(i), i);
}
template<typename T>
struct MyRange: ranges::view_facade<MyRange<T>> {
private:
friend struct ranges::range_access;
std::vector<T> data;
struct cursor {
private:
typename std::vector<T>::const_iterator iter;
public:
cursor() = default;
cursor(typename std::vector<T>::const_iterator it) : iter(it) {}
T const & get() const { return *iter; }
bool equal(cursor const &that) const { return iter == that.iter; }
void next() { ++iter; }
// Don't need those for an InputRange:
// void prev() { --iter; }
// std::ptrdiff_t distance_to(cursor const &that) const { return that.iter - iter; }
// void advance(std::ptrdiff_t n) { iter += n; }
};
cursor begin_cursor() const { return {data.begin()}; }
cursor end_cursor() const { return {data.end()}; }
public:
MyRange() = default;
explicit MyRange(const std::vector<T>& v) : data(v) {}
explicit MyRange(std::vector<T>&& v) noexcept : data (std::move(v)) {}
};
template <typename T>
MyRange<T> to_MyRange(std::vector<T> && v) {
return MyRange<T>(std::forward<std::vector<T>>(v));
}
int main() {
auto src = view::ints(1); // infinite list
auto rng = src | view::transform(f) | view::transform(to_MyRange<int>) | view::join;
for_each(rng | view::take(42), [](int i) {
std::cout << i << ' ';
});
}
// Output:
// 1 2 2 3 3 3 4 4 4 4 5 5 5 5 5 6 6 6 6 6 6 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 9 9 9 9 9 9
gcc 5.3.0으로 컴파일되었습니다.
물론 여기서 문제는 뷰의 전체 아이디어입니다. 비 저장 계층의 게으른 평가자입니다. 이 계약을 따라 잡기 위해 뷰는 범위 요소에 대한 참조를 전달해야하며 일반적으로 rvalue 및 lvalue 참조를 모두 처리 할 수 있습니다.
Unfortunately in this specific case view::transform
can only provide an rvalue reference as your function f(T t)
returns a container by value, and view::join
expects an lvalue as it tries to bind views (view::all
) to inner containers.
Possible solutions will all introduce some kind of temporary storage somewhere into the pipeline. Here are the options I came up with:
- Create a version of
view::all
that can internally store a container passed by an rvalue reference (As suggested by Barry). From my point of view, this violates the "non-storing view" conception and also requires some painful template coding so I would suggest against this option. Use a temporary container for the whole intermediate state after the
view::transform
step. Can be done either by hand:auto rng1 = src | view::transform(f) vector<vector<T>> temp = rng1; auto rng = temp | view::join;
Or using
action::join
. This would result in "premature evaluation", will not work with infinitesrc
, will waste some memory, and overall has a completely different semantics from your original intention, so that is hardly a solution at all, but at least it complies with view class contracts.Wrap a temporary storage around the function you pass into
view::transform
. The simpliest example isconst std::vector<T>& f_store(const T& t) { static std::vector<T> temp; temp = f(t); return temp; }
and then pass
f_store
to theview::transform
. Asf_store
returns an lvalue reference,view::join
will not complain now.This of course is somewhat of a hack and will only work if you then streamline the whole range into some sink, like an output container. I believe it will withstand some straightforward transformations, like
view::replace
or moreview::transform
s, but anything more complex can try to access thistemp
storage in non-straightforward order.In that case other types of storage can be used, e.g.
std::map
will fix that problem and will still allow infinitesrc
and lazy evaluation at the expense of some memory:const std::vector<T>& fc(const T& t) { static std::map<T, vector<T>> smap; smap[t] = f(t); return smap[t]; }
If your
f
function is stateless, thisstd::map
can also be used to potentially save some calls. This approach can possibly be improved further if there is a way to guarantee that an element will no longer be required and remove it from thestd::map
to conserve memory. This however depends on further steps of the pipeline and the evaluation.
As these 3 solutions pretty much cover all the places to introduce temporary storage between view::transform
and view::join
, I think these are all the options you have. I would suggest going with #3 as it will allow you to keep the overall semantics intact and it is quite simple to implement.
This is another solution that doesn't require much fancy hacking. It comes at the cost of a call to std::make_shared
at each call to f
. But you're allocating and populating a container in f
anyway, so maybe this is an acceptable cost.
#include <range/v3/core.hpp>
#include <range/v3/view/iota.hpp>
#include <range/v3/view/transform.hpp>
#include <range/v3/view/join.hpp>
#include <vector>
#include <iostream>
#include <memory>
std::vector<int> f(int i) {
return std::vector<int>(3u, i);
}
template <class Container>
struct shared_view : ranges::view_interface<shared_view<Container>> {
private:
std::shared_ptr<Container const> ptr_;
public:
shared_view() = default;
explicit shared_view(Container &&c)
: ptr_(std::make_shared<Container const>(std::move(c)))
{}
ranges::range_iterator_t<Container const> begin() const {
return ranges::begin(*ptr_);
}
ranges::range_iterator_t<Container const> end() const {
return ranges::end(*ptr_);
}
};
struct make_shared_view_fn {
template <class Container,
CONCEPT_REQUIRES_(ranges::BoundedRange<Container>())>
shared_view<std::decay_t<Container>> operator()(Container &&c) const {
return shared_view<std::decay_t<Container>>{std::forward<Container>(c)};
}
};
constexpr make_shared_view_fn make_shared_view{};
int main() {
using namespace ranges;
auto rng = view::ints | view::transform(compose(make_shared_view, f)) | view::join;
RANGES_FOR( int i, rng ) {
std::cout << i << '\n';
}
}
ReferenceURL : https://stackoverflow.com/questions/36820639/how-do-i-write-a-range-pipeline-that-uses-temporary-containers
'Nice programing' 카테고리의 다른 글
시간 기반 UUID를 생성하는 방법은 무엇입니까? (0) | 2021.01.08 |
---|---|
대표자들을 이해하려는 것이 우주의 본질을 이해하려고하는 것처럼 느껴지는 이유는 무엇입니까? (0) | 2021.01.08 |
std :: fstream 버퍼링 대 수동 버퍼링 (수동 버퍼링으로 10 배 이득)? (0) | 2021.01.08 |
Docker 컨테이너에서 Xcode를 실행할 수 있습니까? (0) | 2021.01.08 |
CLR의 Clojure (0) | 2021.01.08 |