functional_bind

functional_bind (std::bind - 함수 인자 바인딩)

std::bind는 함수 객체의 일부 인자를 고정한 호출 래퍼를 생성하는 함수 템플릿이에요. 이 래퍼를 호출하면 고정된 인자와 함께 원래 함수를 호출하는 것과 동일한 효과를 얻을 수 있어요. std::placeholders의 _1, _2 등을 사용하면 나중에 전달할 인자의 위치를 유연하게 지정할 수도 있답니다.

출처: cppreference

본문

std::bind 함수 템플릿은 f에 대한 전달 호출 래퍼(forwarding call wrapper)를 생성해요. 이 래퍼를 호출하는 것은 f의 일부 인자를 args에 바인딩한 상태로 f를 호출하는 것과 동일해요.

만약 std::is_constructible<std::decay<F>::type, F>::valuefalse이거나, Args에 있는 임의의 타입 Arg_i에 대해 std::is_constructible<std::decay<Arg_i>::type, Arg_i>::valuefalse라면, 프로그램은 ill-formed예요.

std::decay<Ti>::type이나 Args의 어떤 타입이 MoveConstructible 또는 Destructible 요구 사항을 만족하지 못하면, 동작은 정의되지 않아요.

<functional> 헤더에 정의됨
template < class F , class ... Args > /* unspecified */ bind ( F && f , Args && ... args ); (1) (C++11부터) (C++20부터 constexpr)
template < class R , class F , class ... Args > /* unspecified */ bind ( F && f , Args && ... args ); (2) (C++11부터) (C++20부터 constexpr)

매개변수

f - 일부 인자에 바인딩될 호출 가능 객체 (함수 객체, 함수 포인터, 함수 참조, 멤버 함수 포인터, 또는 데이터 멤버 포인터)
args - 바인딩할 인자 목록. 바인딩되지 않은 인자는 std::placeholders 네임스페이스의 _1, _2, _3 ... 자리 표시자로 대체돼요.

반환값

std::is_bind_expression<T>::valuetrue인 지정되지 않은 타입 T의 함수 객체 g예요. 이 객체는 다음과 같은 멤버들을 가져요.

std::bind 반환 타입

멤버 객체

std::bind의 반환 타입은 std::forward<F>(f)로부터 생성된 std::decay<F>::type 타입의 멤버 객체와, 각각의 args...에 대해 std::forward<Arg_i>(arg_i)로부터 유사하게 생성된 std::decay<Arg_i>::type 타입의 객체 하나씩을 보유해요.

생성자

std::bind의 반환 타입은 위에 명시된 모든 멤버 객체가 CopyConstructible이면 CopyConstructible이고, 그렇지 않으면 MoveConstructible이에요. 이 타입은 다음 멤버들을 정의해요.

멤버 타입 result_type 1) (C++17에서 폐기됨) F가 함수 포인터 또는 멤버 함수 포인터라면, result_typeF의 반환 타입이에요. F가 중첩 typedef result_type을 가진 클래스 타입이라면, result_typeF::result_type이에요. 그 외에는 result_type이 정의되지 않아요. 2) (C++17에서 폐기됨) result_type은 정확히 R이에요. (C++20까지)

멤버 함수 operator()

g가 함수 호출 표현식 g(u1, u2, ... uM)에서 호출되면, 저장된 객체에 대한 호출이 마치 INVOKE(fd, v1, v2, ..., vN) 또는 INVOKE<R>(fd, v1, v2, ..., vN)인 것처럼 수행돼요. 여기서 fdstd::decay<F>::type 타입의 값이고, 바인딩된 인자 v1, v2, ..., vN의 값과 타입은 아래에 명시된 대로 결정돼요.

g() 호출에 제공된 인자 중 g에 저장된 어떤 자리 표시자와도 일치하지 않는 인자가 있다면, 사용되지 않은 인자는 평가되고 버려져요.

operator()의 호출은 기본 INVOKE 연산이 비 throwing이거나 상수 부분 표현식(C++20부터)인 경우에만 비 throwing이거나 상수 부분 표현식이에요. operator()는 INVOKE 연산이 평가되지 않은 피연산자로 취급될 때 잘 형성된(well-formed) 경우에만 오버로드 해석에 참여해요.

g가 volatile 한정이면 프로그램은 ill-formed예요.

INVOKE(fd, w1, w2, ..., wN)이 가능한 어떤 값 w1, w2, ..., wN에 대해서도 유효한 표현식이 될 수 없다면, 동작은 정의되지 않아요.

바인딩된 인자

각 저장된 인자 arg_i에 대해, INVOKE 또는 INVOKE 연산에서 대응하는 바인딩된 인자 v_i는 다음과 같이 결정돼요.

Case 1: 참조 래퍼

arg_istd::reference_wrapper<T> 타입이라면 (예를 들어 std::bind를 처음 호출할 때 std::refstd::cref를 사용한 경우), v_iarg_i.get()이고 그 타입 V_iT&예요. 저장된 인자는 호출된 함수 객체에 참조로 전달돼요.

Case 2: bind 표현식

arg_istd::is_bind_expression<T>::valuetrue인 타입 T라면 (예를 들어 다른 std::bind 표현식이 std::bind를 처음 호출할 때 직접 전달된 경우), std::bind는 함수 합성(function composition)을 수행해요. bind 하위 표현식이 반환할 함수 객체를 전달하는 대신, 하위 표현식이 즉시 호출되고 그 반환 값이 외부 호출 가능 객체에 전달돼요. bind 하위 표현식에 자리 표시자 인자가 있다면, 그 인자들은 외부 bind와 공유돼요 (u1, u2, ...에서 선택됨). 구체적으로, v_iarg_i(std::forward<Uj>(uj)...)이고 그 타입 V_istd::result_of<T cv & (Uj &&...)>::type && (C++17까지) 또는 std::invoke_result_t<T cv &, Uj &&...>&& (C++17부터)예요. cv 한정은 g와 동일해요.

Case 3: 자리 표시자

arg_istd::is_placeholder<T>::value가 0이 아닌 타입 T라면 (즉, std::placeholders::_1, _2, _3, ... 같은 자리 표시자가 std::bind를 처음 호출할 때 인자로 사용된 경우), 자리 표시자가 가리키는 인자 (_1이면 u1, _2이면 u2 등)가 호출 가능 객체에 전달돼요. v_istd::forward<Uj>(uj)이고 그 타입 V_iUj&&예요.

Case 4: 일반 인자

그 외의 경우, arg_i는 호출 가능 객체에 lvalue 인자로 전달돼요. v_i는 단순히 arg_i이고 그 타입 V_iT cv &예요. 여기서 cvg와 동일한 cv 한정이에요.

예외

std::forward<F>(f)로부터 std::decay<F>::type을 생성할 때 던지거나, Args... args에서 Arg_i가 i번째 타입이고 arg_i가 i번째 인자일 때 해당 std::forward<Arg_i>(arg_i)로부터 std::decay<Arg_i>::type을 생성하는 생성자 중 하나가 던질 때만 예외를 발생시켜요.

참고

Callable에서 설명한 대로, 비정적 멤버 함수 포인터나 비정적 데이터 멤버 포인터를 호출할 때 첫 번째 인자는 멤버에 접근할 객체에 대한 참조 또는 포인터 (std::shared_ptr이나 std::unique_ptr 같은 스마트 포인터도 가능)여야 해요.

bind에 전달된 인자는 복사되거나 이동되며, std::refstd::cref로 감싸지 않는 한 절대 참조로 전달되지 않아요.

같은 bind 표현식에서 중복된 자리 표시자 (예: 여러 개의 _1)는 허용되지만, 결과가 잘 정의되려면 대응하는 인자 (u1)가 lvalue이거나 이동 불가능한 rvalue여야 해요.

예제

#include <functional>
#include <iostream>
#include <memory>
#include <random>

void f(int n1, int n2, int n3, const int& n4, int n5)
{
    std::cout << n1 << ' ' << n2 << ' ' << n3 << ' ' << n4 << ' ' << n5 << '\n';
}

int g(int n1)
{
    return n1;
}

struct Foo
{
    void print_sum(int n1, int n2)
    {
        std::cout << n1 + n2 << '\n';
    }
    
    int data = 10;
};

int main()
{
    using namespace std::placeholders;  // for _1, _2, _3...
    
    std::cout << "1) argument reordering and pass-by-reference: ";
    int n = 7;
    // (_1 and _2 are from std::placeholders, and represent future
    // arguments that will be passed to f1)
    auto f1 = std::bind(f, _2, 42, _1, std::cref(n), n);
    n = 10;
    f1(1, 2, 1001); // 1 is bound by _1, 2 is bound by _2, 1001 is unused
                    // makes a call to f(2, 42, 1, n, 7)
    
    std::cout << "2) achieving the same effect using a lambda: ";
    n = 7;
    auto lambda = [&ncref = n, n](auto a, auto b, auto /*unused*/)
    {
        f(b, 42, a, ncref, n);
    };
    n = 10;
    lambda(1, 2, 1001); // same as a call to f1(1, 2, 1001)
    
    std::cout << "3) nested bind subexpressions share the placeholders: ";
    auto f2 = std::bind(f, _3, std::bind(g, _3), _3, 4, 5);
    f2(10, 11, 12); // makes a call to f(12, g(12), 12, 4, 5);
    
    std::cout << "4) bind a RNG with a distribution: ";
    std::default_random_engine e;
    std::uniform_int_distribution<> d(0, 10);
    auto rnd = std::bind(d, e); // a copy of e is stored in rnd
    for (int n = 0; n < 10; ++n)
        std::cout << rnd() << ' ';
    std::cout << '\n';
    
    std::cout << "5) bind to a pointer to member function: ";
    Foo foo;
    auto f3 = std::bind(&Foo::print_sum, &foo, 95, _1);
    f3(5);
    
    std::cout << "6) bind to a mem_fn that is a pointer to member function: ";
    auto ptr_to_print_sum = std::mem_fn(&Foo::print_sum);
    auto f4 = std::bind(ptr_to_print_sum, &foo, 95, _1);
    f4(5);
    
    std::cout << "7) bind to a pointer to data member: ";
    auto f5 = std::bind(&Foo::data, _1);
    std::cout << f5(foo) << '\n';
    
    std::cout << "8) bind to a mem_fn that is a pointer to data member: ";
    auto ptr_to_data = std::mem_fn(&Foo::data);
    auto f6 = std::bind(ptr_to_data, _1);
    std::cout << f6(foo) << '\n';
    
    std::cout << "9) use smart pointers to call members of the referenced objects: ";
    std::cout << f6(std::make_shared<Foo>(foo)) << ' '
              << f6(std::make_unique<Foo>(foo)) << '\n';
}

출력:

1) argument reordering and pass-by-reference: 2 42 1 10 7
2) achieving the same effect using a lambda: 2 42 1 10 7
3) nested bind subexpressions share the placeholders: 12 12 12 4 5
4) bind a RNG with a distribution: 0 1 8 5 5 2 0 7 7 10 
5) bind to a pointer to member function: 100
6) bind to a mem_fn that is a pointer to member function: 100
7) bind to a pointer to data member: 10
8) bind to a mem_fn that is a pointer to data member: 10
9) use smart pointers to call members of the referenced objects: 10 10

결함 보고서

다음 동작 변경 결함 보고서는 이전에 발표된 C++ 표준에 소급 적용되었어요.

DR 적용 대상 게시된 동작 올바른 동작
LWG 2021 C++11 1. 바인딩된 인자가 fd로 전달되지 않았음 2. case 2에서 V_i의 타입은 std::result_of<T cv (Uj...)>::type이었음 1. 전달됨 2. std::result_of<T cv & (Uj &&...)>::type &&로 변경됨

같이 보기

bind_front bind_back (C++20) (C++23) 함수 객체에 인자를 순서대로 가변 개수만큼 바인딩해요 (함수 템플릿) [편집]
_1, _2, _3, _4, ... (C++11) std::bind 표현식에서 바인딩되지 않은 인자를 위한 자리 표시자 (상수) [편집]
mem_fn (C++11) 멤버 포인터로부터 함수 객체를 만들어요 (함수 템플릿) [편집]

더 알아보기 (Learn more)

cppreference