functional_invoke
functional_invoke (std::invoke와 std::invoke_r)
std::invoke는 함수, 함수 객체, 멤버 함수 포인터, 멤버 객체 포인터 등을 주어진 인자와 함께 호출하는 C++ 표준 유틸리티예요. std::invoke_r는 반환 타입을 명시적으로 지정할 수 있는 변형으로, 결과를 원하는 타입으로 변환하거나 void로 처리할 수 있어요. 두 함수 모두 C++17 이후부터 사용할 수 있으며, constexpr 지원이 추가되었어요.
출처: cppreference
본문
개요
헤더 <functional>에 정의됨 |
||
|---|---|---|
template < class F , class ... Args > std :: invoke_result_t < F , Args ... > invoke ( F && f , Args && ... args ) noexcept ( /* see below */ ); |
(1) | (C++17부터) (C++20부터 constexpr) |
template < class R , class F , class ... Args > constexpr R invoke_r ( F && f , Args && ... args ) noexcept ( /* see below */ ); |
(2) | (C++23부터) |
매개변수 (Parameters)
| f | - | 호출할 호출 가능 객체(Callable object) |
|---|---|---|
| args | - | f에 전달할 인자들 |
반환값 (Return value)
f를 호출한 결과를 반환해요. std::invoke_r의 경우, 반환값을 R 타입으로 변환한 결과를 돌려줘요. R이 void라면 아무것도 반환하지 않아요.
예외 (Exceptions)
noexcept ( std :: is_nothrow_invocable_v < F , Args ... > )noexcept ( std :: is_nothrow_invocable_r_v < R , F , Args ... > )
즉, 호출 가능 객체가 예외를 던지지 않는다고 알려진 경우에만 noexcept로 처리돼요.
가능한 구현 (Possible implementation)
invoke (1)의 가능한 구현은 다음과 같아요.
namespace detail {
template < class > constexpr bool is_reference_wrapper_v = false ;
template < class U > constexpr bool is_reference_wrapper_v < std :: reference_wrapper < U >> = true ;
template < class T > using remove_cvref_t = std :: remove_cv_t < std :: remove_reference_t < T >> ;
template < class C , class Pointed , class Object , class ... Args >
constexpr decltype ( auto ) invoke_memptr ( Pointed C ::* member , Object && object , Args && ... args )
{
using object_t = remove_cvref_t < Object > ;
constexpr bool is_member_function = std :: is_function_v < Pointed > ;
constexpr bool is_wrapped = is_reference_wrapper_v < object_t > ;
constexpr bool is_derived_object = std :: is_same_v < C , object_t > || std :: is_base_of_v < C , object_t > ;
if constexpr ( is_member_function )
{
if constexpr ( is_derived_object )
return ( std :: forward < Object > ( object ) . * member ) ( std :: forward < Args > ( args )...);
else if constexpr ( is_wrapped )
return ( object . get () . * member )( std :: forward < Args > ( args )...);
else
return (( * std :: forward < Object > ( object )) . * member ) ( std :: forward < Args > ( args )...);
}
else
{
static_assert ( std :: is_object_v < Pointed > && sizeof ...( args ) == 0 );
if constexpr ( is_derived_object )
return std :: forward < Object > ( object ) . * member ;
else if constexpr ( is_wrapped )
return object . get () . * member ;
else
return ( * std :: forward < Object > ( object )) . * member ;
}
}
} // namespace detail
template < class F , class ... Args >
constexpr std :: invoke_result_t < F , Args ... > invoke ( F && f , Args && ... args )
noexcept ( std :: is_nothrow_invocable_v < F , Args ... > )
{
if constexpr ( std :: is_member_pointer_v < detail :: remove_cvref_t < F >> )
return detail :: invoke_memptr ( f , std :: forward < Args > ( args )...);
else
return std :: forward < F > ( f )( std :: forward < Args > ( args )...);
}
invoke_r (2)의 가능한 구현은 다음과 같아요.
template < class R , class F , class ... Args >
requires std :: is_invocable_r_v < R , F , Args ... >
constexpr R invoke_r ( F && f , Args && ... args )
noexcept ( std :: is_nothrow_invocable_r_v < R , F , Args ... > )
{
if constexpr ( std :: is_void_v < R > )
std :: invoke ( std :: forward < F > ( f ), std :: forward < Args > ( args )...);
else
return std :: invoke ( std :: forward < F > ( f ), std :: forward < Args > ( args )...);
}
참고 (Notes)
| 기능 테스트 매크로 | 값 | 표준 | 기능 |
|---|---|---|---|
__cpp_lib_invoke |
201411L | (C++17) | std::invoke, (1) |
__cpp_lib_invoke_r |
202106L | (C++23) | std::invoke_r, (2) |
예제 (Example)
#include <functional>
#include <iostream>
#include <type_traits>
struct Foo
{
Foo(int num) : num_(num) {}
void print_add(int i) const { std::cout << num_ + i << '\n'; }
int num_;
};
void print_num(int i)
{
std::cout << i << '\n';
}
struct PrintNum
{
void operator()(int i) const
{
std::cout << i << '\n';
}
};
int main()
{
std::cout << "invoke a free function: ";
std::invoke(print_num, -9);
std::cout << "invoke a lambda: ";
std::invoke([](){ print_num(42); });
std::cout << "invoke a member function: ";
const Foo foo(314159);
std::invoke(&Foo::print_add, foo, 1);
std::cout << "invoke (i.e., access) a data member num_: "
<< std::invoke(&Foo::num_, foo) << '\n';
std::cout << "invoke a function object: ";
std::invoke(PrintNum(), 18);
#if defined(__cpp_lib_invoke_r)
auto add = [](int x, int y){ return x + y; };
std::cout << "invoke a lambda converting result to float: ";
auto ret = std::invoke_r<float>(add, 11, 22);
static_assert(std::is_same<decltype(ret), float>());
std::cout << std::fixed << ret << "\ninvoke print_num: ";
std::invoke_r<void>(print_num, 44);
#endif
}
가능한 출력:
invoke a free function: -9
invoke a lambda: 42
invoke a member function: 314160
invoke (i.e., access) a data member num_: 314159
invoke a function object: 18
invoke a lambda converting result to float: 33.000000
invoke print_num: 44
같이 보기 (See also)
mem_fn (C++11) |
멤버 포인터로부터 함수 객체를 생성해요 (함수 템플릿) [edit] |
|---|---|
apply (C++17) |
튜플의 인자들로 함수를 호출해요 (함수 템플릿) [edit] |
apply_result (C++26) |
주어진 반영된 튜플 인자들로 호출 가능 객체를 호출한 결과 타입을 추론해요 (함수) [edit] |
apply_result (C++26) |
주어진 튜플 인자들로 호출 가능 객체를 호출한 결과 타입을 추론해요 (클래스 템플릿) [edit] |
is_applicable is_nothrow_applicable (C++26) (C++26) |
주어진 튜플 인자들로 std::invoke처럼 호출할 수 있는지 확인해요 (클래스 템플릿) [edit] |
is_applicable_type is_nothrow_applicable_type (C++26) (C++26) |
반영된 타입을 주어진 반영된 튜플 인자들로 std::invoke처럼 호출할 수 있는지 확인해요 (함수) [edit] |
result_of invoke_result (C++11) (removed in C++20) (C++17) |
인자 집합으로 호출 가능 객체를 호출한 결과 타입을 추론해요 (클래스 템플릿) [edit] |
is_invocable is_invocable_r is_nothrow_invocable is_nothrow_invocable_r (C++17) (C++17) (C++17) (C++17) |
주어진 인자 타입들로 std::invoke처럼 호출할 수 있는지 확인해요 (클래스 템플릿) [edit] |
is_invocable_type is_invocable_r_type is_nothrow_invocable_type is_nothrow_invocable_r_type (C++26) (C++26) (C++26) (C++26) |
반영된 타입을 주어진 인자 타입들로 std::invoke처럼 호출할 수 있는지 확인해요 (함수 템플릿) [edit] |