3

C++20 introduced ranges::elements_view, which accepts a view of tuple-like values, and issues a view with a value-type of the Nth element of the adapted view's value-type, where N is the non-type template parameter.

In [range.elements.view], the synopsis of ranges::elements_view is defined as:

template<input_­range V, size_t N>
  requires view<V> && has-tuple-element<range_value_t<V>, N> &&
           has-tuple-element<remove_reference_t<range_reference_t<V>>, N> &&
           returnable-element<range_reference_t<V>, N>
class elements_view : public view_interface<elements_view<V, N>> {
  public:
    elements_view() = default;
    constexpr explicit elements_view(V base);
  // ...
};

Since the constructor of elements_view only contains the parameter V, it is impossible to initialize elements_view directly without specifying all template parameters (godbolt):

std::array<std::tuple<int, float>, 5> r{};

ranges::elements_view<0>{r};  // error: wrong number of template arguments (1, should be 2)
ranges::elements_view<decltype(views::all(r)), 0>{r}; // ok
views::elements<0>(r);                                // ok

This makes it lose the expression equivalence of other range adaptors such as ranges::transform_view:

ranges::transform_view{r, [](auto elem) { return std::get<0>(elem); }}; // ok
views::transform(r, [](auto elem) { return std::get<0>(elem); });       // ok

And on the basis of elements_view, the standard additionally introduces keys_view and values_view, which are both aliases for elements_­view<views​::​all_­t<R>, N>:

template <class R>
using keys_view = elements_view<views::all_t<R>, 0>;
template <class R>
using values_view = elements_view<views::all_t<R>, 1>;

The standard gives its use cases in [range.elements#overview-example-2]:

auto historical_figures = map{
  {"Lovelace"sv, 1815},
  {"Turing"sv, 1912},
  {"Babbage"sv, 1791},
  {"Hamilton"sv, 1936}
};

auto names = keys_view{historical_figures}; 
for (auto&& name : names) {   
  cout << name << ' ';          // prints Babbage Hamilton Lovelace Turing  
} 

which is not quite right since we cannot deduce the type of R, the only way to construct keys_view is to specify all template parameters, just like elements_view:

auto names = std::ranges::keys_view<decltype(std::views::all(historical_figures))>{historical_figures};

So keys_view and values_view seem to be completely useless.

What is the purpose of introducing them in the standard? Did I miss some of their useful use cases?

康桓瑋
  • 33,481
  • 5
  • 40
  • 90
  • 1
    I think this is just an implementation issue. It *should* work, and in fact `auto names = historical_figures | std::ranges::views::keys;` compiles just fine. [demo](https://godbolt.org/z/os6fdMTqo) – cigien May 29 '21 at 15:24
  • @cigen. I don't think it should work, at least I haven't figured out how to make it work. – 康桓瑋 May 29 '21 at 15:38
  • What makes you think it *shouldn't* work though? The standard says it should, so I would definitely bet on a compiler bug vs a bug in the standard. – cigien May 29 '21 at 15:39
  • 1
    @康桓瑋: FYI: "*This makes it lose the expression equivalence of other range adaptors*" Not [the way equivalence is defined in this case](http://eel.is/c++draft/range.elements#overview-2). – Nicol Bolas May 29 '21 at 15:40
  • 1
    @cigien. "_The standard says it should_". If you take a closer look at [range. elements#overview-2](http://eel.is/c++draft/range.elements#overview-2), you will find that `historical_figures` [cannot](https://godbolt.org/z/63E4W37d8) be initialized without specifying a `pair`. – 康桓瑋 May 29 '21 at 15:48

1 Answers1

7

The missing pair issue in the example is just a bug with the example; I submitted an editorial pull request.

The bigger problem is with keys_view and values_view's definitions. An LWG issue has been submitted for which I have provided a proposed resolution. The basic issue here is that

template <class R>
using keys_view = elements_view<views::all_t<R>, 0>;

uses R only in a non-deduced context, so it can't possibly ever meet the requirement in alias template CTAD that the arguments of the alias template be deducible from whatever type that is eventually deduced.

My proposed resolution simply redefines keys_view to be

template <class R>
using keys_view = elements_view<R, 0>;

This allows CTAD to work if you already have a view, and at least gives it a chance of working with whatever deduction guide on elements_view that is added later. I explored the possibility of adding such guides, but the current alias template CTAD implementations are too broken for it to be tested, so I don't feel comfortable proposing one.

With this change, you would be able to at least write

auto names = keys_view{views::all(historical_figures)}; 
for (auto&& name : names) {   
  cout << name << ' ';          // prints Babbage Hamilton Lovelace Turing  
} 
T.C.
  • 133,968
  • 17
  • 288
  • 421