2016-12-07 1 views
6

대신 잘못된 과부하를 선택 : 두 번째 search_with 호출, 컴파일러는 세 번째 오버로드를 선택하는 이유컴파일러는이 코드를 살펴 유효 과부하

#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! 
} 

내가 이해가 안 돼요. 세 번째 오버로드를 주석 처리하면 코드가 잘 컴파일됩니다. 이는 두 번째 오버로드가 컴파일 될 때 무시되지 않고 유효한 오버로드 여야 함을 나타냅니다. 반복자 std::begin (및 std::end)의 더 전문화가 없으므로

그러나, 제 3 오버가 실패하는 선택된다 :

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 반대 상황이 발생한다고 생각했을

: 제 과부하 폐기 컴파일에 실패하고 두 번째가 선택되기 때문에.

하지만 분명히 그렇지 않습니다. 그래서 여기서 무슨 일이 일어나고 있습니까? 잘못된 과부하가 선택된 이유는 무엇입니까? 두 번째 것보다 세 번째 오버로드가 더 좋은 이유는 무엇입니까?

+1

SFINAE는 defintion이 아닌 함수 선언에 적용됩니다.이 때문에 enable_if가 템플릿 인수 또는 반환 값에 적용되는 이유는 이러한 코드를 본문에 부착하는 더 쉬운 옵션이 아니기 때문입니다. 선언 비트가 컴파일되지 않는 경우에만 함수가 무시됩니다. 제공된 인수는 세 번째 함수의 선언에서 올바르게 작동하므로 삭제되지 않습니다. – jaggedSpire

+0

그러나 후행 반환 형식을 사용하면 작동하는 것으로 보입니다. http://coliru.stacked-crooked.com/a/5d3d34290d7beb1a – AndyG

+0

@AndyG 아, 그건 SFINAE입니다. :) 감사합니다 – Rakete1111

답변

7

대개 rvalue 인 세 번째 인수를 사용해야합니다. 범용 참조와 더 잘 어울리는 이유를 보려면 다음을 시도하십시오. 그것은 또한 당신에게 완벽하게 일치를 제공하기 때문에

#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와 똑같이 일치합니다.따라서 예제에서 어떤 과부하가 선택되었는지는 알 수 없습니다.

+0

재미있는 점은 OP의 코드가 값에 의해 'pred'로 선택 되었기 때문에 부분 순서가 시작되어 올바른 오버로드를 선택했을 것입니다. –

+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은 rvalue이고 세 번째 오버로드에서 Pred &&는 두 번째보다 더 나은 일치 일 수 있습니다. – Tomek

+0

참조로 전달 움직이는 것보다 선호합니다 (코드가 적게 포함됨). rvalue는 const-reference 매개 변수에 할당 될 수 있습니다. 그래서 이것은 여전히 ​​값으로 전달하기 위해 여분의 로직을 호출하는 대신에 const- 레퍼런스를 통해 반복자를 전달하는 것을 선호하는 컴파일러의 문제라고 생각합니다. –

2

세 번째 매개 변수로 함수형 값을 함수 템플릿에 전달한 경우를 제외하고는 세 번째 오버로드가 항상 더 좋습니다. 당신은 평범한 가치를 전달합니다. 전달 참조 Pred&&은이 사례와 더 잘 일치 할 수 있으므로 선택됩니다.

SFINAE (Substitution Failure Is Not An Error) 기술을 사용하여 원하는 동작을 얻을 수 있습니다. decltype(...)에서 표현식이 유효하지 않은 경우

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

는 과부하 제외됩니다.

+0

중복을 제거하는 것 외에 다른 방법이 없다고 생각합니다. 맞습니까? – Rakete1111

+0

@ Rakete1111 불행히도. 어쩌면 미래의 C++ 표준이 될 수도 있지만 적절한 SFINAE 친화적 인 기능을 원한다면 당장은 추악한 환경에서 살아야 할 것입니다. –