Reputation: 42746
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?
Upvotes: 3
Views: 696
Reputation: 137310
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
}
Upvotes: 8