Zizheng Tai
Zizheng Tai

Reputation: 6616

range_value_t for reference type

I was playing with C++ 20 ranges and noticed something weird (reproducible at https://gcc.godbolt.org/z/4Ycxn88qa):

#include <vector>
#include <utility>
#include <string>
#include <ranges>
#include <type_traits>

int main()
{
    using KV = std::pair<std::string, std::string>;

    std::vector<KV> v;
    auto r = v | std::views::filter([](const KV& kv) { return kv.first == "foo"; })
               | std::views::transform([](const KV& kv) -> const std::string& { return kv.second; });

    for (auto&& val : r) {
        static_assert(std::is_same_v<decltype(val), const std::string&>);
    }

    using R = decltype(r);
    using Elem = std::ranges::range_value_t<R>;

    static_assert(std::is_same_v<Elem, std::string>);  // success
    static_assert(std::is_same_v<Elem, const std::string&>);  // error
}

I expected the last static_assert to succeed and the second to last to fail, but the reverse is true. Why?

UPDATE: Apparently range_reference_t gives me what I need:

static_assert(std::is_same_v<std::ranges::range_reference_t<R>, const std::string&>); // ok

Is range_value_t supposed to always give me a decayed type?

Upvotes: 7

Views: 1557

Answers (1)

Barry
Barry

Reputation: 303057

Is range_value_t supposed to always give me a decayed type?

The value_type of an iterator (and, consequently, a range) should always be a cv-unqualified type, yes. That's just the way that the model works. This isn't really specified in the standard library anywhere to my knowledge, but you can see that the value_type of T* is remove_cv_t<T> ([iterators.iterator.traits]/5) or that the way we get the value_type of transform_view's iterators is by doing a remove_cvref_t on the reference type ([range.transform.iterator]).

reference, on the other hand, is the type that you get when you dereference the iterator - literally decltype(*it). Unfortunately, this one is confusingly named because reference is not necessarily a language type.

For instance, int* is basically the simplest iterator: its value_type is int while its reference is int&. Similarly, for int const*, the value_type is the same (still int) while the reference changes (to int const&).

Upvotes: 10

Related Questions