types_result_of

types_result_of (호출 결과 타입 추론)

std::result_of는 컴파일 타임에 주어진 호출 가능한 타입과 인자 목록으로 INVOKE 표현식의 반환 타입을 추론하는 템플릿이에요. C++11에서 도입되었고 C++17에서 비권장(deprecated)되었으며 C++20에서 제거되었어요. 이후에는 std::invoke_result를 사용하는 것이 권장돼요.

출처: cppreference

본문

정의

<type_traits> 헤더에 정의되어 있어요.

템플릿 정의 버전 비고
template < class > class result_of; // 정의되지 않음
template < class F, class ... ArgTypes > class result_of<F(ArgTypes...)>;
(1) C++11부터, C++17에서 비권장, C++20에서 제거
template < class F, class ... ArgTypes > class invoke_result; (2) C++17부터

이 템플릿은 컴파일 타임에 INVOKE 표현식의 반환 타입을 추론해요.

제약 조건

조건 적용 범위
F는 호출 가능한 타입, 함수에 대한 참조, 또는 호출 가능한 타입에 대한 참조여야 해요. ArgTypes...F를 호출하는 것은 유효한 표현식이어야 해요. C++11부터 C++14까지
FArgTypes...의 모든 타입은 완전 타입(complete type), 경계를 알 수 없는 배열, 또는 (cv 한정된) void일 수 있어요. C++14부터

이 페이지에 설명된 템플릿에 대해 프로그램이 특수화를 추가하면 동작이 정의되지 않아요.

멤버 타입

멤버 타입 설명
type 호출 가능한 타입 FArgTypes... 인자로 호출했을 때의 반환 타입이에요. FArgTypes...로 비평가 문맥(unevaluated context)에서 호출할 수 있을 때만 정의돼요. (C++14부터)

헬퍼 타입

헬퍼 타입 버전 비고
template < class T > using result_of_t = typename result_of<T>::type; (1) C++14부터, C++17에서 비권장, C++20에서 제거
template < class F, class ... ArgTypes > using invoke_result_t = typename invoke_result<F, ArgTypes...>::type; (2) C++17부터

가능한 구현

namespace detail
{
    template<class T>
    struct is_reference_wrapper : std::false_type {};
    template<class U>
    struct is_reference_wrapper<std::reference_wrapper<U>> : std::true_type {};
 
    template<class T>
    struct invoke_impl
    {
        template<class F, class... Args>
        static auto call(F&& f, Args&&... args)
            -> decltype(std::forward<F>(f)(std::forward<Args>(args)...));
    };

    template<class B, class MT>
    struct invoke_impl<MT B::*>
    {
        template<class T, class Td = typename std::decay<T>::type,
            class = typename std::enable_if<std::is_base_of<B, Td>::value>::type>
        static auto get(T&& t) -> T&&;

        template<class T, class Td = typename std::decay<T>::type,
            class = typename std::enable_if<is_reference_wrapper<Td>::value>::type>
        static auto get(T&& t) -> decltype(t.get());

        template<class T, class Td = typename std::decay<T>::type,
            class = typename std::enable_if<!std::is_base_of<B, Td>::value>::type,
            class = typename std::enable_if<!is_reference_wrapper<Td>::value>::type>
        static auto get(T&& t) -> decltype(*std::forward<T>(t));

        template<class T, class... Args, class MT1,
            class = typename std::enable_if<std::is_function<MT1>::value>::type>
        static auto call(MT1 B::*pmf, T&& t, Args&&... args)
            -> decltype((invoke_impl::get(
                std::forward<T>(t)).*pmf)(std::forward<Args>(args)...));

        template<class T>
        static auto call(MT B::*pmd, T&& t)
            -> decltype(invoke_impl::get(std::forward<T>(t)).*pmd);
    };

    template<class F, class... Args, class Fd = typename std::decay<F>::type>
    auto INVOKE(F&& f, Args&&... args)
        -> decltype(invoke_impl<Fd>::call(std::forward<F>(f),
            std::forward<Args>(args)...));
} // namespace detail

// Minimal C++11 implementation:
template<class> struct result_of;
template<class F, class... ArgTypes>
struct result_of<F(ArgTypes...)>
{
    using type = decltype(detail::INVOKE(std::declval<F>(), std::declval<ArgTypes>()...));
};

// Conforming C++14 implementation (is also a valid C++11 implementation):
namespace detail
{
    template<typename AlwaysVoid, typename, typename...>
    struct invoke_result {};
    template<typename F, typename...Args>
    struct invoke_result<
        decltype(void(detail::INVOKE(std::declval<F>(), std::declval<Args>()...))),
            F, Args...>
    {
        using type = decltype(detail::INVOKE(std::declval<F>(), std::declval<Args>()...));
    };
} // namespace detail

template<class> struct result_of;
template<class F, class... ArgTypes>
struct result_of<F(ArgTypes...)> : detail::invoke_result<void, F, ArgTypes...> {};

template<class F, class... ArgTypes>
struct invoke_result : detail::invoke_result<void, F, ArgTypes...> {};

참고 사항

C++11에서 정의된 대로, INVOKE(std::declval<F>(), std::declval<ArgTypes>()...)가 유효하지 않은 표현식일 때(예: F가 전혀 호출 가능한 타입이 아닐 때) std::result_of의 동작은 정의되지 않아요. C++14에서는 이를 SFINAE로 변경해서, F를 호출할 수 없을 때 std::result_of<F(ArgTypes...)>는 단순히 type 멤버를 갖지 않게 돼요.

std::result_of가 만들어진 동기는 호출 가능한 객체를 호출한 결과 타입을 알아내기 위한 것이에요. 특히 인자 집합에 따라 결과 타입이 달라질 수 있는 경우에 유용해요.

F(Args...)는 함수 타입으로, Args...는 인자 타입, F는 반환 타입을 의미해요. 그래서 std::result_of는 다음과 같은 여러 가지 특이한 점이 있었고, 이 때문에 C++17에서 std::invoke_result로 대체되어 비권장 처리되었어요.

  • F는 함수 타입이나 배열 타입이 될 수 없어요. (단, 이들에 대한 참조는 가능해요.)
  • Args 중 어떤 타입이 "T의 배열" 또는 함수 타입 T라면 자동으로 T*로 조정돼요.
  • FArgs... 중 어떤 것도 추상 클래스 타입이 될 수 없어요.
  • Args... 중 어떤 타입에 최상위 cv 한정자가 있으면 무시돼요.
  • Args... 중 어떤 것도 void 타입이 될 수 없어요.

이런 특이점을 피하기 위해 result_ofFArgs...에 참조 타입을 사용하는 경우가 많아요. 예를 들어:

template<class F, class... Args>
std::result_of_t<F&&(Args&&...)> // instead of std::result_of_t<F(Args...)>, which is wrong
    my_invoke(F&& f, Args&&... args)
    {
        /* implementation */
    }

기능 테스트 매크로

기능 테스트 매크로 표준 기능
__cpp_lib_result_of_sfinae 201210L (C++14) std::result_of와 SFINAE
__cpp_lib_is_invocable 201703L (C++17) std::is_invocable, std::invoke_result

예제

#include <iostream>
#include <type_traits>

struct S
{
    double operator()(char, int&);
    float operator()(int) { return 1.0; }
};

template<class T>
typename std::result_of<T(int)>::type f(T& t)
{
    std::cout << "overload of f for callable T\n";
    return t(0);
}

template<class T, class U>
int f(U u)
{
    std::cout << "overload of f for non-callable T\n";
    return u;
}

int main()
{
    // the result of invoking S with char and int& arguments is double
    std::result_of<S(char, int&)>::type d = 3.14; // d has type double
    static_assert(std::is_same<decltype(d), double>::value, "");
    
    // std::invoke_result uses different syntax (no parentheses)
    std::invoke_result<S,char,int&>::type b = 3.14;
    static_assert(std::is_same<decltype(b), double>::value, "");

    // the result of invoking S with int argument is float
    std::result_of<S(int)>::type x = 3.14; // x has type float
    static_assert(std::is_same<decltype(x), float>::value, "");

    // result_of can be used with a pointer to member function as follows
    struct C { double Func(char, int&); };
    std::result_of<decltype(&C::Func)(C, char, int&)>::type g = 3.14;
    static_assert(std::is_same<decltype(g), double>::value, "");

    f<C>(1); // may fail to compile in C++11; calls the non-callable overload in C++14
}

출력:

overload of f for non-callable T

같이 보기

함수/템플릿 설명
invoke, invoke_r (C++17) (C++23) 주어진 인자로 호출 가능한 객체를 호출하고 반환 타입을 지정할 수 있어요 (함수 템플릿)
apply (C++17) 튜플 인자로 함수를 호출해요 (함수 템플릿)
declval (C++11) 비평가 문맥에서 사용하기 위해 템플릿 타입 인자의 객체에 대한 참조를 얻어요 (함수 템플릿)
is_invocable, is_invocable_r, is_nothrow_invocable, is_nothrow_invocable_r (C++17) 타입이 주어진 인자 타입으로 (마치 std::invoke처럼) 호출될 수 있는지 확인해요 (클래스 템플릿)
is_applicable, is_nothrow_applicable (C++26) 타입이 주어진 튜플 인자로 (마치 std::invoke처럼) 호출될 수 있는지 확인해요 (클래스 템플릿)
apply_result (C++26) 주어진 튜플 인자로 호출 가능한 객체를 호출한 결과 타입을 추론해요 (클래스 템플릿)
apply_result (C++26) 주어진 반영된 튜플 인자로 호출 가능한 객체를 호출한 결과 타입을 추론해요 (함수)
invoke_result (C++26) 인자 집합으로 호출 가능한 객체를 호출한 결과 타입을 추론해요 (함수 템플릿)

더 알아보기 (Learn more)

cppreference