看看这段代码:
#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!
}
我不明白为什么在第二次search_with
调用中,编译器会选择第三个重载。如果我注释掉第三个重载,那么代码编译得很好。这表明第二个重载在编译时没有被丢弃,它应该是一个有效的重载。
但是,选择了第三个重载,因为没有std::begin
(和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));
~~~~~~~~~~^~~~~~~
我原以为会发生相反的情况:第三次重载被丢弃,因为它无法编译,第二次重载被选中。
但事实显然不是这样,所以这里发生了什么?为什么选择了错误的过载?为什么第三个超载比第二个超载更好?
答案 0 :(得分:7)
它主要是做第三个参数,这是一个右值。请尝试以下方法,以了解它与更好地符合通用参考的原因。
#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
}
如果再引发一次重载,
template <typename T>
inline void f(T);
进入混音,你会得到模棱两可的错误,因为它也会给你完美的匹配。
关于前两个rvalue参数,请考虑以下示例
template <typename T>
inline void f(T)
{
}
template <typename T>
inline void f(const T &)
{
}
int main() { f(1); }
你会收到一个模棱两可的错误。也就是说,两个重载同样匹配rvalue。因此,他们不会确定您的示例中选择了哪个重载
答案 1 :(得分:2)
除非将const lvalue作为第三个参数传递给函数模板,否则第三个重载总是更好。你传了一个prvalue。转发参考Pred&&
可以更好地匹配此案例,因此可以选择。
您可以使用SFINAE (Substitution Failure 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));
}
如果decltype(...)
中的表达式无效,则会排除重载。
答案 2 :(得分:0)
第一个调用是传递满足只有一个重载的模板参数的参数值:
template<typename Array, typename T, typename Pred>
auto search_with(const Array& array, const T& value, Pred&& pred) noexcept
第二个调用是传递满足 two 重载的模板参数的参数值:
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...
但是,第三个重载是一个更好的匹配,因为它的参数都是通过引用传递的,所以编译器不必创建不必要的副本。
对于第二个重载,前两个参数按值传递,因此必须进行额外的复制。处理类对象(可能是STL容器的iterator
类型)时,可能会影响重载决策。
编译器尽可能避免制作不必要的副本。