Useless's answer is very complete; but just to simplify it a bit... There are exactly two things you could have meant with your original code. Either you meant to invoke ADL on begin
, or you didn't.
If you don't mean to invoke ADL, then you should use a qualified name. You wouldn't write sort(v.begin(), v.end())
when you meant std::sort
, so you shouldn't write begin(v)
when you mean std::begin(v)
, either. In fact, you shouldn't write std::begin(v)
when you mean v.begin()
!
template<class T>
auto test(const std::vector<T>& v) {
return std::begin(v); // Right (no ADL)
}
template<class T>
auto test(const std::vector<T>& v) {
return v.begin(); // Even better (no ADL)
}
If you do mean to invoke ADL, then you must enable ADL by adding a using
-declaration. Typically you use ADL when you're writing generic code (templates), and you want the author of T
to have some control over what happens in your templatey code. In your actual code snippet, v
is just a std::vector<int>
with no templatey bits; but for the sake of argument let's pretend that its type is some unknown container type T
(perhaps even a native array type, like int[10]
). Then we should do the std::swap
two-step:
template<class T>
auto test(const T& v) {
using std::begin;
return begin(v); // Right (ADL)
}
These ("qualified" and "ADL two-step") are the only two ways of making non-member function calls in C++ that you need to know; they are designed to work 100% of the time. Your original snippet deviated from the best practice by trying to use ADL without the two-step; that's why it sometimes failed for you.
Note that in C++20 you can (if you like) make a qualified call to std::ranges::begin
, which will itself do the ADL two-step for you:
template<class T>
auto test(const T& v) {
return std::ranges::begin(v); // Right (ADL happens internally)
}