반환 유형 추론이 std :: is_invocable_v로 SFINAE를 지원할 수없는 이유

Nov 19 2020

람다 식을 포함하여 반환 유형 추론 기능 이있는 일부 펑터가 있습니다.

constexpr auto a = [](auto&& x){ return x + 1; };
struct X{
    template<typename T>
    auto operator()(T&& x){
        return x + 1;
    }
};

그런 다음 인수가 이러한 펑터에 적용될 수 있는지 확인하는 두 가지 함수, by std::is_invocable_vand 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)오류가 발생합니까?

답변

3 dfrib Nov 19 2020 at 15:29

이것은 [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되는, (반환 유형을 찾기 위해) 구현으로 인해 특수화에 대해 반환 유형 추론이 트리거되며,이 경우 특수화를 인스턴스화해야합니다. 이 경우 위의 (비 규범 적) 노트에서 강조된 것처럼 프로그램이 잘못 구성되었습니다.