Reputation: 11
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
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 vector
s 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
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_iterator
s 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 c++14 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