2016-12-07 32 views
6

的這段代碼看看:編譯器錯誤的選擇,而不是過載超載有效

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

我會想到,發生相反的情況:所述第三過載被丟棄因爲它無法編譯,而第二個選擇。

但是,顯然不是這樣,所以這裏發生了什麼?爲什麼選擇了錯誤的過載?爲什麼第三次超載會比第二次更好呢?

+1

SFINAE適用於函數聲明,而不是定義,這就是爲什麼enable_if應用於模板參數或返回值,而不是在主體中粘貼這樣的代碼的漂亮選項。只有在聲明位編譯失敗的情況下,該函數纔會被丟棄。提供的參數在第三個函數的聲明中工作得很好,所以它不會被丟棄。 – jaggedSpire

+0

但是,如果您使用尾隨返回類型,它似乎工作:http://coliru.stacked-crooked.com/a/5d3d34290d7beb1a – AndyG

+0

@AndyG啊,那就是SFINAE。 :)謝謝 – Rakete1111

回答

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); 

混進去,你會得到曖昧的錯誤,因爲它也會給你完美的比賽。

至於前兩個右值參數,請看下面的例子,

template <typename T> 
inline void f(T) 
{ 
} 

template <typename T> 
inline void f(const T &) 
{ 
} 

int main() { f(1); } 

,你會得到一個明確的錯誤。也就是說,這兩個過載與一個右值相等。所以他們不能確定在你的例子中選擇了哪一個過載

+0

更有趣的是,如果OP的代碼被價值「預測」,部分排序會被踢進去,並選擇正確的過載。 –

+0

@ T.C。究竟。起初功能過載很有趣。但是,如果可能的話,今天我只是使用標籤重載來節省很多頭痛。 C++重載的問題是,即使經過多年的努力,當你知道它的進出時,你仍然可能不時忘記一些事情,並將它弄錯。將普遍的參照引入混合只會使其變得更糟。我們是人類,而不是編譯器。 –

0

第一呼叫被傳遞滿足的模板參數參數值只有一個過載:

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... 

但是,第三個重載是一個更好的匹配,因爲它的參數都是通過引用傳遞的,所以編譯器不需要創建不必要的副本。

第二次重載時,前兩個參數按值傳遞,所以必須額外拷貝。處理類對象(STL容器的iterator類型可能)時,可能會影響重載解析。

編譯器會盡可能避免進行不必要的複製。

+0

編譯器爲什麼不移動迭代器而不是複製它們? – Rakete1111

+1

我只是在想,這是第三個參數有什麼區別... 10是一個右值,在第三次過載中Pred &&可能是一個更好的匹配,然後const T&在第二次... – Tomek

+0

Pass-by-reference仍然優於移動(涉及較少的代碼)。並且右值可以被分配給常量引用參數。所以我認爲這仍然是編譯器喜歡通過const-reference傳遞迭代器的問題,而不是調用額外的邏輯來按值傳遞它們。 –

2

第三個重載總是比較好,除非你傳遞一個const的左值作爲函數模板的第三個參數。你通過一個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(...)中的表達式無效。

+0

我想除了刪除重複之外別無他法,對吧? – Rakete1111

+0

@ Rakete1111不幸的是。也許在未來的C++標準中,但是現在,如果你想要正確的SFINAE友好函數,你將不得不忍受醜陋。 –

相關問題