编译器选择错误的过载而不是有效的过载

时间:2016-12-07 20:35:43

标签: c++ c++14 overload-resolution

看看这段代码:

#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));
                        ~~~~~~~~~~^~~~~~~

我原以为会发生相反的情况:第三次重载被丢弃,因为它无法编译,第二次重载被选中。

但事实显然不是这样,所以这里发生了什么?为什么选择了错误的过载?为什么第三个超载比第二个超载更好?

3 个答案:

答案 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类型)时,可能会影响重载决策。

编译器尽可能避免制作不必要的副本。