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]

더 알아보기 (Learn more)

cppreference