들어가며
가변 인자 템플릿 함수를 사용하는데, 함수 bind 할 때 마다 인자 갯수 세서 placeholder 붙여 주기 귀찮아 죽겠다. 어차피 파라미터 팩에서 인자 갯수며 타입 다 알수 있는데 컴파일러가 placeholder까지 같이 달아 주면 안되나?
ㅇㅇ 됨. placeholder 특징만 알고 있음 됨
1. std::placeholders::_1, _2 … 대신 고유 타입의 커스텀 플레이스홀더를 정의한다.
2. std::is_placeholder 를 특수화하여 해당 타입이 “N번째 인자”로 동작하도록 만든다.
3. std::integer_sequence (혹은 직접 만든 )로 인자 개수만큼 플레이스홀더를 자동 생성해 std::bind에 전달한다.
C++ 표준 functional 헤더를 보면 placeholder는 아래처럼 _Ph<N> 과 같은 타입으로 정의되어 있음
namespace placeholders {
_EXPORT_STD _INLINE_VAR constexpr _Ph<1> _1{};
_EXPORT_STD _INLINE_VAR constexpr _Ph<2> _2{};
_EXPORT_STD _INLINE_VAR constexpr _Ph<3> _3{};
_EXPORT_STD _INLINE_VAR constexpr _Ph<4> _4{};
_EXPORT_STD _INLINE_VAR constexpr _Ph<5> _5{};
_EXPORT_STD _INLINE_VAR constexpr _Ph<6> _6{};
_EXPORT_STD _INLINE_VAR constexpr _Ph<7> _7{};
_EXPORT_STD _INLINE_VAR constexpr _Ph<8> _8{};
_EXPORT_STD _INLINE_VAR constexpr _Ph<9> _9{};
_EXPORT_STD _INLINE_VAR constexpr _Ph<10> _10{};
_EXPORT_STD _INLINE_VAR constexpr _Ph<11> _11{};
_EXPORT_STD _INLINE_VAR constexpr _Ph<12> _12{};
_EXPORT_STD _INLINE_VAR constexpr _Ph<13> _13{};
_EXPORT_STD _INLINE_VAR constexpr _Ph<14> _14{};
_EXPORT_STD _INLINE_VAR constexpr _Ph<15> _15{};
_EXPORT_STD _INLINE_VAR constexpr _Ph<16> _16{};
_EXPORT_STD _INLINE_VAR constexpr _Ph<17> _17{};
_EXPORT_STD _INLINE_VAR constexpr _Ph<18> _18{};
_EXPORT_STD _INLINE_VAR constexpr _Ph<19> _19{};
_EXPORT_STD _INLINE_VAR constexpr _Ph<20> _20{};
} // namespace placeholders
_Ph는 placeholder의 약자 같은데 아래 처럼 is_placehoder로 사용됨. 대신 쟤는 integral_constant가 1 부터 시작하는데 우리가 사용할 index_sequence는 0부터 시작함.
template <int _Nx>
struct _Ph { // placeholder
static_assert(_Nx > 0, "invalid placeholder index");
};
_EXPORT_STD template <class _Tx>
struct is_placeholder : integral_constant<int, 0> {}; // _Tx is not a placeholder
template <int _Nx>
struct is_placeholder<_Ph<_Nx>> : integral_constant<int, _Nx> {}; // _Ph is a placeholder
그래서
namespace std {
template <int N>
struct is_placeholder<variadic_placeholder<N>>
: integral_constant<int, N + 1>
{
};
}
이런 커스텀 플레이스 홀더를 하나 선언해줌. std::placeholder와는 다르게 N + 1이 되어 있는 것을 확인할 수 있음.
#include <iostream>
#include <functional>
// 1) 커스텀 플레이스홀더 타입
template <int>
struct variadic_placeholder {};
// 2) std::is_placeholder 특수화
namespace std {
template <int N>
struct is_placeholder<variadic_placeholder<N>>
: integral_constant<int, N + 1>
{
};
}
template <class R, class ...Args, std::size_t... Is>
auto variadic_bind(R(*func_ptr)(Args...), std::index_sequence<Is...>)
{
// variadic_placeholder<Is>{} 가 std::bind에 넘겨지는 순간
// std::is_placeholder 특수화 덕분에 _1, _2, _3… 처럼 동작
return std::bind(func_ptr, variadic_placeholder<Is>{}...);
}
template <class R, class ...Args>
auto variadic_bind(R(*func_ptr)(Args...))
{
// 3) 인덱스 시퀀스 (C++14 이상은 std::index_sequence 사용 가능)
return variadic_bind(func_ptr, std::make_index_sequence<sizeof...(Args)>{});
}
template <class R, class Class, class ...Args, std::size_t... Is>
auto variadic_bind(R(Class::*func_ptr)(Args...), Class* obj, std::index_sequence<Is...>)
{
return std::bind(func_ptr, obj, variadic_placeholder<Is>{}...);
}
template <class R, class Class, class ...Args>
auto variadic_bind(R(Class::*func_ptr)(Args...), Class* obj)
{
return variadic_bind(func_ptr, obj, std::make_index_sequence<sizeof...(Args)>{});
}
void free_function(int i, const std::string& greeting)
{
std::cout << i << ":" << greeting << std::endl;
}
class Foo
{
public :
void member_function(int i, const std::string& greeting)
{
std::cout << i << ":" << greeting << std::endl;
}
};
int main()
{
auto f = variadic_bind(&free_function);
f(1, "hello global function");
Foo foo;
auto mf = variadic_bind(&Foo::member_function, &foo);
mf(2, "hello member function");
}