编译器选择错误的重载而不是有效的重载
Compiler chooses erroneous overload instead of valid overload
看看这段代码:
#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));
~~~~~~~~~~^~~~~~~
我本以为会发生相反的情况:第三个重载因为编译失败而被丢弃,而选择了第二个。
但显然不是这样,那么这里发生了什么?为什么选择了错误的重载?为什么第三个重载比第二个更匹配?
第一个调用传递的参数值满足 只有一个 重载的模板参数:
template<typename Array, typename T, typename Pred>
auto search_with(const Array& array, const T& value, Pred&& pred) noexcept
第二次调用传递的参数值满足两个重载的模板参数:
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...
然而,第三个重载是更好的匹配,因为它的参数都是通过引用传递的,所以编译器不必创建不必要的副本。
在第二次重载时,前两个参数按值传递,因此必须进行额外的复制。在处理 class 对象(可能是 STL 容器的 iterator
类型)时,这会影响重载解析。
编译器尽可能避免制作不必要的副本。
它主要是做第三个参数,它是一个右值。试试下面的方法,看看为什么它能更好地匹配通用参考。
#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);
混进去,你会得到模棱两可的错误,因为它也会给你绝配。
关于前两个右值参数,请考虑以下示例,
template <typename T>
inline void f(T)
{
}
template <typename T>
inline void f(const T &)
{
}
int main() { f(1); }
你会得到一个不明确的错误。也就是说,两个重载同样匹配右值。所以他们不确定在你的例子中选择了哪个重载
第三个重载总是更好,除非您将 const 左值作为第三个参数传递给您的函数模板。你传递了一个纯右值。转发参考 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(...)
中的表达式无效,这将排除重载。
看看这段代码:
#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));
~~~~~~~~~~^~~~~~~
我本以为会发生相反的情况:第三个重载因为编译失败而被丢弃,而选择了第二个。
但显然不是这样,那么这里发生了什么?为什么选择了错误的重载?为什么第三个重载比第二个更匹配?
第一个调用传递的参数值满足 只有一个 重载的模板参数:
template<typename Array, typename T, typename Pred>
auto search_with(const Array& array, const T& value, Pred&& pred) noexcept
第二次调用传递的参数值满足两个重载的模板参数:
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...
然而,第三个重载是更好的匹配,因为它的参数都是通过引用传递的,所以编译器不必创建不必要的副本。
在第二次重载时,前两个参数按值传递,因此必须进行额外的复制。在处理 class 对象(可能是 STL 容器的 iterator
类型)时,这会影响重载解析。
编译器尽可能避免制作不必要的副本。
它主要是做第三个参数,它是一个右值。试试下面的方法,看看为什么它能更好地匹配通用参考。
#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);
混进去,你会得到模棱两可的错误,因为它也会给你绝配。
关于前两个右值参数,请考虑以下示例,
template <typename T>
inline void f(T)
{
}
template <typename T>
inline void f(const T &)
{
}
int main() { f(1); }
你会得到一个不明确的错误。也就是说,两个重载同样匹配右值。所以他们不确定在你的例子中选择了哪个重载
第三个重载总是更好,除非您将 const 左值作为第三个参数传递给您的函数模板。你传递了一个纯右值。转发参考 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(...)
中的表达式无效,这将排除重载。