Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

c++20 ranges library, how to make conditional operator work?

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.

like image 435
QnA Avatar asked Oct 14 '22 20:10

QnA


2 Answers

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;
}
like image 193
eerorika Avatar answered Oct 20 '22 16:10

eerorika


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.

like image 39
Yakk - Adam Nevraumont Avatar answered Oct 20 '22 16:10

Yakk - Adam Nevraumont