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까지 |
F와 ArgTypes...의 모든 타입은 완전 타입(complete type), 경계를 알 수 없는 배열, 또는 (cv 한정된) void일 수 있어요. |
C++14부터 |
이 페이지에 설명된 템플릿에 대해 프로그램이 특수화를 추가하면 동작이 정의되지 않아요.
멤버 타입
| 멤버 타입 | 설명 |
|---|---|
type |
호출 가능한 타입 F를 ArgTypes... 인자로 호출했을 때의 반환 타입이에요. F를 ArgTypes...로 비평가 문맥(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*로 조정돼요.F나Args...중 어떤 것도 추상 클래스 타입이 될 수 없어요.Args...중 어떤 타입에 최상위 cv 한정자가 있으면 무시돼요.Args...중 어떤 것도void타입이 될 수 없어요.
이런 특이점을 피하기 위해 result_of는 F와 Args...에 참조 타입을 사용하는 경우가 많아요. 예를 들어:
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) |
인자 집합으로 호출 가능한 객체를 호출한 결과 타입을 추론해요 (함수 템플릿) |