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>::value가 false이거나, Args에 있는 임의의 타입 Arg_i에 대해 std::is_constructible<std::decay<Arg_i>::type, Arg_i>::value가 false라면, 프로그램은 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>::value가 true인 지정되지 않은 타입 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_type은 F의 반환 타입이에요. F가 중첩 typedef result_type을 가진 클래스 타입이라면, result_type은 F::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)인 것처럼 수행돼요. 여기서 fd는 std::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 또는 INVOKEv_i는 다음과 같이 결정돼요.
Case 1: 참조 래퍼
arg_i가 std::reference_wrapper<T> 타입이라면 (예를 들어 std::bind를 처음 호출할 때 std::ref나 std::cref를 사용한 경우), v_i는 arg_i.get()이고 그 타입 V_i는 T&예요. 저장된 인자는 호출된 함수 객체에 참조로 전달돼요.
Case 2: bind 표현식
arg_i가 std::is_bind_expression<T>::value가 true인 타입 T라면 (예를 들어 다른 std::bind 표현식이 std::bind를 처음 호출할 때 직접 전달된 경우), std::bind는 함수 합성(function composition)을 수행해요. bind 하위 표현식이 반환할 함수 객체를 전달하는 대신, 하위 표현식이 즉시 호출되고 그 반환 값이 외부 호출 가능 객체에 전달돼요. bind 하위 표현식에 자리 표시자 인자가 있다면, 그 인자들은 외부 bind와 공유돼요 (u1, u2, ...에서 선택됨). 구체적으로, v_i는 arg_i(std::forward<Uj>(uj)...)이고 그 타입 V_i는 std::result_of<T cv & (Uj &&...)>::type && (C++17까지) 또는 std::invoke_result_t<T cv &, Uj &&...>&& (C++17부터)예요. cv 한정은 g와 동일해요.
Case 3: 자리 표시자
arg_i가 std::is_placeholder<T>::value가 0이 아닌 타입 T라면 (즉, std::placeholders::_1, _2, _3, ... 같은 자리 표시자가 std::bind를 처음 호출할 때 인자로 사용된 경우), 자리 표시자가 가리키는 인자 (_1이면 u1, _2이면 u2 등)가 호출 가능 객체에 전달돼요. v_i는 std::forward<Uj>(uj)이고 그 타입 V_i는 Uj&&예요.
Case 4: 일반 인자
그 외의 경우, arg_i는 호출 가능 객체에 lvalue 인자로 전달돼요. v_i는 단순히 arg_i이고 그 타입 V_i는 T cv &예요. 여기서 cv는 g와 동일한 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::ref나 std::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) |
멤버 포인터로부터 함수 객체를 만들어요 (함수 템플릿) [편집] |