Apologies for the title, if I knew how to better phrase it then google probably already helped me...
I would like to have an object Y, that represents a view of container X, so that when I iterate over Y, it's either forward or backward iteration of X. I would like to do it without copying the data, hence the new ranges
library comes into mind.
std::vector x{};
auto z = some_condition ? x : (x | std::views::reverse);
Apparently the types of x
and (x|...)
are different. How can I make them consistent?
Edit: just found the following question asked 10 years ago, I guess what I am trying to find out is, does ranges
make things easier now? Since that solution still requires the for-loop logic to be put into a separate function or lambda.
Apparently the types of x and (x|...) are different. How can I make them consistent?
You could make them consistent by using a type-erasing view for the ranges.
However, you must decide whether the potential runtime cost of such view is worth the goal that you're trying to achieve.
does ranges make things easier now?
It doesn't have an effect on this as far as I can tell. The same issue exists with iterators as well as ranges. Both can be worked around using type-erasure at the cost of potential runtime overhead.
Standard library doesn't provide an implementation of such type erasing range, nor a type-erasing iterator so you'll have to write your own (or as nearly always, use one written by someone else).
You can alternatively solve the problem with ranges the analogous way as in the linked iterator question, by avoiding their use in a single expression:
if (some_condition) {
auto z = x | std::views::all;
} else {
auto z = x | std::views::reverse;
}
I'd package them up in a variant.
First write:
template<class...Ts, class V=std::variant<std::decay_t<Ts>...>>
V pick(std::size_t i, Ts&&...ts );
that returns a variant with the ith argument held.
Then:
auto z = pick(some_condition?0:1, std::views::all(x), x | std::views::reverse);
Now your code runs via std::visit
.
std::visit( [&](auto&& elems){
for( auto&& elem: elems ) {
// loop
}
}, z );
pick
implementation:
namespace impl {
template<class...Ts, std::size_t...Is, class V=std::variant<std::decay_t<Ts>...>>
V pick(std::index_sequence<Is...>, std::size_t i, Ts&&...ts )
{
using pF = V(*)(std::tuple<Ts&&...>);
const pF pickers[] = {
+[](std::tuple<Ts&&...> t)->V{
return V( std::in_place_index<Is>, std::get<Is>(std::move(t)) );
}...
};
return pickers[i]( std::forward_as_tuple(std::forward<Ts>(ts)...) );
}
}
template<class...Ts, class V=std::variant<std::decay_t<Ts>...>>
V pick(std::size_t i, Ts&&...ts ) {
return impl::pick( std::make_index_sequence<sizeof...(Ts)>{}, i, std::forward<Ts>(ts)... );
}
and a lazy-evaluation variant:
namespace impl {
template<class...Fs, std::size_t...Is, class V=std::variant<std::invoke_result_t<Fs>...>>
V lazy_pick(std::index_sequence<Is...>, std::size_t i, Fs&&...fs )
{
using pF = V(*)(std::tuple<Fs&&...>);
const pF pickers[] = {
+[](std::tuple<Fs&&...> t)->V{
return V( std::in_place_index<Is>, std::get<Is>(std::move(t))() );
}...
};
return pickers[i]( std::forward_as_tuple(std::forward<Fs>(fs)...) );
}
}
template<class...Fs, class V=std::variant<std::invoke_result_t<Fs>...>>
V lazy_pick(std::size_t i, Fs&&...fs ) {
return impl::lazy_pick( std::make_index_sequence<sizeof...(Fs)>{}, i, std::forward<Fs>(fs)... );
}
Live example.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With