The compiler chooses the wrong overload instead of the actual overload

Take a look at this code:

#include <vector> #include <functional> template<typename RandIt, typename T, typename Pred> auto search_with(RandIt begin, RandIt end, const T& value, Pred&& pred) noexcept { //... return begin; } template<typename RandIt, typename T> auto search_with(RandIt begin, RandIt end, const T& value) noexcept { return search_with(begin, end, value, std::less<T>{}); } template<typename Array, typename T, typename Pred> auto search_with(const Array& array, const T& value, Pred&& pred) noexcept { return search_with(std::begin(array), std::end(array), value, std::forward<Pred>(pred)); } int main() { std::vector<int> v = { 1, 2, 3 }; search_with(v, 10, std::less<int>{}); // ok search_with(v.begin(), v.end(), 10); // fail! } 

I donโ€™t understand why in the second call to search_with compiler chooses the third overload. If I comment on the third overload, then the code compiles fine. This indicates that the second overload is not discarded as it is compiled and should be a valid overload.

However, a third overload was selected, which does not execute, since there is no specialization for iterators std::begin (and std::end ):

 main.cpp: In instantiation of 'auto search_with(const Array&, const T&, Pred&&) [with Array = __gnu_cxx::__normal_iterator<int*, std::vector<int> >; T = __gnu_cxx::__normal_iterator<int*, std::vector<int> >; Pred = int]': main.cpp:23:39: required from here main.cpp:17:34: error: no matching function for call to 'begin(const __gnu_cxx::__normal_iterator<int*, std::vector<int> >&)' return search_with(std::begin(array), std::end(array), value, std::forward<Pred>(pred)); ~~~~~~~~~~^~~~~~~ 

I would think that the opposite is happening: the third overload is discarded because it does not compile, and the second is selected.

But this is obviously not the case, so what happens here? Why is the wrong overload selected? Why is the third overload better than the second?

+6
source share
3 answers

Basically it should do the third argument, which is an rvalue. Try the following to understand why it better matches a universal link.

 #include <iostream> template <typename T> inline void f(T &) { std::cout << "f2" << std::endl; } template <typename T> inline void f(const T &) { std::cout << "f3" << std::endl; } template <typename T> inline void f(T &&) { std::cout << "f4" << std::endl; } int main() { int a = 0; const int b = 0; int &c = a; const int &d = b; f(1); // f4 : rvalue matched by universal reference f(a); // f2 : lvalue matched by reference, T& preferred to const T& f(b); // f3 : lvalue matched by reference, can only do const T& f(c); // f2 : lvalue reference matched by reference, T& preferred to const T& f(d); // f3 : lvalue const reference matched by reference, can only do const T& f(std::move(a)); // f4 : rvalue reference, matched by universal reference } 

If you throw one more overload,

  template <typename T> inline void f(T); 

in the mix, you will get mixed errors because it will also give you a perfect match.

Regarding the first two arguments of rvalue, consider the following example:

 template <typename T> inline void f(T) { } template <typename T> inline void f(const T &) { } int main() { f(1); } 

You will get an ambiguous error. That is, two overloads equally correspond to the value of r. Therefore, they do not determine which overload is selected in your example.

+7
source

The third overload is always better, except that you pass the lvalue constant as the third parameter to your function template. You convey the due. The Pred&& forwarding Pred&& may be better suited to this case and therefore is selected.

You can achieve the desired behavior using the SFINAE method (Replacement error is not an error) .

 template<typename Array, typename T, typename Pred> auto search_with(const Array& array, const T& value, Pred&& pred) noexcept -> decltype(search_with( std::begin(array), std::end(array), value, std::forward<Pred>(pred))) { return search_with(std::begin(array), std::end(array), value, std::forward<Pred>(pred)); } 

This eliminates overloading if the expression in decltype(...) invalid.

+2
source

The first call passes parameter values โ€‹โ€‹that satisfy the template arguments of only one overload:

 template<typename Array, typename T, typename Pred> auto search_with(const Array& array, const T& value, Pred&& pred) noexcept 

The second call passes parameter values โ€‹โ€‹that satisfy the template arguments of the two overloads:

 template<typename RandIt, typename T> auto search_with(RandIt begin, RandIt end, const T& value) noexcept // where RandIt is std::vector<int>::iterator, and T is int... 

 template<typename Array, typename T, typename Pred> auto search_with(const Array& array, const T& value, Pred&& pred) noexcept // where Array and T are both std::vector<int>::iterator, and Pred is int... 

However, the third overload is the best match, since all its parameters are passed by reference, so no unnecessary copies should be created by the compiler.

At the second overload, the first two parameters are passed by value, so you need to make additional copies. When working with class objects (which may have the STL iterator container type), this can affect overload resolution.

The compiler tries to avoid making unnecessary copies if possible.

0
source

Source: https://habr.com/ru/post/1012981/


All Articles