Zizheng Tai
Zizheng Tai

Reputation: 6616

Assignable reference to both lvalues and rvalues

Suppose I have a function unique_count that can count the number of unique elements in any unsorted range:

template<typename InputIt>
std::size_t unique_count(InputIt first, InputIt last)
{
    using T = typename std::iterator_traits<InputIt>::value_type;

    std::vector</* ??? */> v(first, last);
    std::sort(v.begin(), v.end());
    return std::distance(v.begin(),
                         std::unique(v.begin(), v.end()));
}

I create a new vector because I don't want to modify the original container, but I also don't want to do unnecessary copying. The easiest solution is to make a vector of pointers, but a problem is *first may return either a const T& (e.g., from a vector), or a T (e.g., from a stream).

The /* ??? */ part should therefore be conceptually equivalent to const T&. We can't really do this, since vectors can't hold references, which are unassignable. We can't use reference_wrapper<const T> either, because it's not constructible from temporaries.

What alternatives do I have?

Upvotes: 0

Views: 70

Answers (1)

Brian Bi
Brian Bi

Reputation: 119184

You want /* ??? */ to be a type that may or may not own a T object, depending on whether it was constructed with a temporary T or a const T&. For obvious reasons, such types should generally be avoided.

One alternative is to use tag dispatch or SFINAE to select one of two possible implementations, depending on whether *first returns a reference or non-reference type.

Another alternative is to narrow your algorithm a bit so that it only accepts forward iterators. Here you are guaranteed to be able to simply store iterators themselves and use them to compare the elements pointed to:

template <typename ForwardIt>
std::size_t unique_count(ForwardIt first, ForwardIt last) {
    std::vector<ForwardIt> v;
    for (auto i = first; i != last; ++i) {
        v.push_back(i);
    }
    const auto pred = [](ForwardIt a, ForwardIt b) { return *a < *b; };
    std::sort(v.begin(), v.end(), pred);
    return std::distance(v.begin(), std::unique(v.begin(), v.end(), pred));
}

Upvotes: 2

Related Questions