반환 유형 추론이 std :: is_invocable_v로 SFINAE를 지원할 수없는 이유
람다 식을 포함하여 반환 유형 추론 기능 이있는 일부 펑터가 있습니다.
constexpr auto a = [](auto&& x){ return x + 1; };
struct X{
template<typename T>
auto operator()(T&& x){
return x + 1;
}
};
그런 다음 인수가 이러한 펑터에 적용될 수 있는지 확인하는 두 가지 함수, by std::is_invocable_v
and SFINAE가 있습니다.
template<typename F, typename T, std::enable_if_t<std::is_invocable_v<F, T>, int> = 0>
void foo(T a){
std::cout << "yes" << std::endl;
}
template<typename F, typename T, std::enable_if_t<!std::is_invocable_v<F, T>, int> = 0>
void foo(T a){
std::cout << "no" << std::endl;
}
마지막으로, 나는 사용 foo<X>(0)
하거나 foo<decltype(a)>(0)
, 그것은 잘 작동하고, 검사가 전달되기 때문에 "예"를 말한다. 내가 사용하는 때 foo<X>((void*)0)
또는 foo<decltype(a)>((void*)0)
, 내가받을 컴파일 오류를 "아니오"대신.
대체는 다음에서 발생합니다.
1. 함수 유형에 사용되는 모든 유형 ( 반환 유형 및 모든 매개 변수 유형 포함 )
...
이 펑 터는 모든 유형의 인수를 받아들이고 형식 x + 1
이 잘못 되면 오류를 발생시킵니다 . 그러나의 반환 유형은 operator()
에 의해 추론 x + 1
되므로 인수의 유형에 따라 다릅니다 T
. 이 때 std::is_invocable_v
인스턴스화, T
치환되어 void*
, 다음의 서명 operator()
이 잘못된 반환 형식을 . 이 대체 실패 입니까?
이 질문을 명확히하기 위해 다음 두 가지 펑터를 정의합니다.
struct Y{
template<typename T>
decltype(auto) operator()(T&& x){
return x + 1;
}
};
struct Z{
template<typename T>
auto operator()(T&& x)->decltype(x + 1){
return x + 1;
}
};
반환 유형이
decltype(auto)
이면 반환 유형은 return 문에 사용 된 표현식이 래핑 된 경우 얻을 수있는 것과 같습니다decltype
.
그러나 왜 foo<Z>((void*)0)
"아니오"라고 말하지만 foo<Y>((void*)0)
오류가 발생합니까?
답변
이것은 [dcl.spec.auto] / 11 입니다.
operator()(...)
추론 된 반환 유형은 반환 유형 추론을 활용 하는 템플릿 화 된 콜 러블 (함수 템플릿 / 제네릭 람다 / 펑터 with templated ) 의 반환 유형을 쿼리 하려면 정의와 같이 콜 러블의 특정 전문화를 인스턴스화해야 한다는 점에서 SFINAE 친화적이지 않습니다. 반환 유형을 추론하려면 전문화가 필요합니다.
[dcl.spec.auto] / 11 선언 된 유형의 자리 표시자가있는 함수 템플릿에 대한 반환 유형 추론은 함수 본문에 유형에 종속되지 않는 피연산자가있는 return 문이 포함되어 있더라도 정의가 인스턴스화 될 때 발생합니다. [ 참고 : 따라서 함수 템플릿의 특수화를 사용하면 암시 적 인스턴스화가 발생합니다. 이 인스턴스화에서 발생하는 모든 오류는 함수 유형의 직접적인 컨텍스트에 있지 않으며 프로그램이 잘못 형성 될 수 있습니다 ([temp.deduct]). — end note] [예 :
template <class T> auto f(T t) { return t; } // return type deduced at instantiation time typedef decltype(f(1)) fint_t; // instantiates f<int> to deduce return type template<class T> auto f(T* t) { return *t; } void g() { int (*p)(int*) = &f; } // instantiates both fs to determine return types, // chooses second
— 최종 예]
호출 가능한 특수화 호출의 (평가되지 않은) 표현식에 std::is_invocable
적용 decltype
되는, (반환 유형을 찾기 위해) 구현으로 인해 특수화에 대해 반환 유형 추론이 트리거되며,이 경우 특수화를 인스턴스화해야합니다. 이 경우 위의 (비 규범 적) 노트에서 강조된 것처럼 프로그램이 잘못 구성되었습니다.