zmfgouk
zmfgouk

Reputation: 11

Removing cv-qualifier from templated vector function

I'm writing some code that randomly picks an element from a given vector with any generic type. I've successfully made a function that returns a copy of a random element, but it'd be convenient for me if there's a similar function that returns the iterator of that element instead. This is what I have, in which the random iterator is yielded from adding a random number to the begin iterator through a generator function:

template <class T>
typename std::vector<T>::iterator pickElementIterator(const std::vector<T>& v)
{
    if (v.empty())
    {
        // error
        return v.end();
    }

    return v.begin() + generate(v.size());
}

This compiles successfully if no code uses it. But as soon as I try to substitute in some vector with type Foo, the following error comes up using g++:

could not convert '(& v)->std::vector::end()' from '__normal_iterator<const Foo*,[...]>' to
__normal_iterator<Foo*,[...]>'

For both returns. It seems it adds a const to the type when attempting to convert it for returning. I've tried std::decay on both returns but it does not accept those arguments. That's as far as my knowledge on template programming goes, and not much about this is found anywhere I looked.

Note: "typename" is in the return type because otherwise g++ complains with:

need 'typename' before 'std::vector<_RealType>::iterator' because 'std::vector<_RealType>' is a dependent scope Which is still not clear for me.

Upvotes: 1

Views: 225

Answers (2)

Ted Lyngmo
Ted Lyngmo

Reputation: 117433

I suggest that you let auto decide what kind of iterator that is returned. If you use C++11 you can use a trailing return type like decltype(v.begin()) and in C++14 and later, that's not needed.

If you also make use of template template parameters, you don't have to make it work for vectors only. v.begin() + generate(v.size()) requires random access though, so I suggest using std::next to get the resulting iterator.

Example:

#include <iterator> // std::next

template <template <class, class...> class C, class T, class... Args>
auto pickElementIterator(const C<T, Args...>& c) -> decltype(c.begin()) {
    if(c.empty()) return c.end();
    return std::next(c.begin(), generate(c.size()));
}

Upvotes: 0

Bitwize
Bitwize

Reputation: 11220

std::vector<T>::begin()/std::vector<T>::end() return a const_iterator when const qualified, not an iterator (which is mutable).

The reason this fails to compile is because const_iterators cannot be converted to mutable iterator types. The fix is simple: change the return type to be const_iterator:

template <class T>
typename std::vector<T>::const_iterator pickElementIterator(const std::vector<T>& v)
//                       ^~~~~~~~~~~~~~

If you're in or above, you can also just use auto without a declared return type so that it can deduce it itself, e.g.:

template <class T>
auto pickElementIterator(const std::vector<T>& v)

Note: The reason you observed that this compiled successfully when not used is because templates can't fully be evaluated until instantiated. The compiler has to assume that anything that relies on a template parameter (for example, type T) might produce a valid instantiation thanks to template specialization.

For example, the compiler must assume that there may exist a T such that const std::vector<T>::begin() returns something which could construct a std::vector<T>::iterator. This could happen if vector<T> were specialized.

As a result, you won't often see many diagnostics outside of syntax errors until you're actually instantiating a template

Upvotes: 1

Related Questions