일치하는 함수 포인터를 호출하기 위해 튜플 "풀기"
나는 std::tuple
다양한 수의 값 으로 저장하려고 노력하고 있는데, 나중에 저장된 유형과 일치하는 함수 포인터를 호출하기위한 인수로 사용됩니다.
해결하기 위해 고심하고있는 문제를 보여주는 간단한 예를 만들었습니다.
#include <iostream>
#include <tuple>
void f(int a, double b, void* c) {
std::cout << a << ":" << b << ":" << c << std::endl;
}
template <typename ...Args>
struct save_it_for_later {
std::tuple<Args...> params;
void (*func)(Args...);
void delayed_dispatch() {
// How can I "unpack" params to call func?
func(std::get<0>(params), std::get<1>(params), std::get<2>(params));
// But I *really* don't want to write 20 versions of dispatch so I'd rather
// write something like:
func(params...); // Not legal
}
};
int main() {
int a=666;
double b = -1.234;
void *c = NULL;
save_it_for_later<int,double,void*> saved = {
std::tuple<int,double,void*>(a,b,c), f};
saved.delayed_dispatch();
}
일반적으로 문제가 std::tuple
있거나 다양한 템플릿 template <typename Head, typename ...Tail>
을 사용하는 경우 모든 유형을 하나씩 재귀 적으로 평가하는 것과 같은 다른 템플릿을 작성 하지만 함수 호출을 전달하는 방법을 볼 수는 없습니다.
이것에 대한 진정한 동기는 다소 복잡하며 어쨌든 대부분 학습 운동입니다. 다른 인터페이스와의 계약으로 튜플을 건네 주었으므로 변경할 수는 없지만 함수 호출로 압축을 풀고 싶은 욕구는 내 것입니다. 이는 std::bind
근본적인 문제를 회피하기위한 저렴한 방법으로 사용 하지 않습니다.
을 사용하여 호출을 전달하는 깔끔한 방법 std::tuple
또는 임의의 미래 시점까지 일부 값과 함수 포인터를 저장 / 전달하는 동일한 결과를 얻는 더 좋은 방법은 무엇입니까?
당신은 숫자의 매개 변수 팩을 작성하고 포장을 풀어야합니다
template<int ...>
struct seq { };
template<int N, int ...S>
struct gens : gens<N-1, N-1, S...> { };
template<int ...S>
struct gens<0, S...> {
typedef seq<S...> type;
};
// ...
void delayed_dispatch() {
callFunc(typename gens<sizeof...(Args)>::type());
}
template<int ...S>
void callFunc(seq<S...>) {
func(std::get<S>(params) ...);
}
// ...
C ++ 17 솔루션은 다음을 사용하는 것입니다 std::apply
.
auto f = [](int a, double b, std::string c) { std::cout<<a<<" "<<b<<" "<<c<< std::endl; };
auto params = std::make_tuple(1,2.0,"Hello");
std::apply(f, params);
이 스레드에서 답변에 한 번 언급해야한다고 느꼈습니다 (이미 의견 중 하나에 나타난 후).
이 스레드에는 기본 C ++ 14 솔루션이 여전히 없습니다. 편집 : 아니요, 실제로 Walter의 답변에 있습니다.
이 기능은 다음과 같습니다.
void f(int a, double b, void* c)
{
std::cout << a << ":" << b << ":" << c << std::endl;
}
다음 스 니펫으로 호출하십시오.
template<typename Function, typename Tuple, size_t ... I>
auto call(Function f, Tuple t, std::index_sequence<I ...>)
{
return f(std::get<I>(t) ...);
}
template<typename Function, typename Tuple>
auto call(Function f, Tuple t)
{
static constexpr auto size = std::tuple_size<Tuple>::value;
return call(f, t, std::make_index_sequence<size>{});
}
예:
int main()
{
std::tuple<int, double, int*> t;
//or std::array<int, 3> t;
//or std::pair<int, double> t;
call(f, t);
}
이것은 누군가에게 유용 할 수 있기를 희망하며, awoodland의 질문에 대한 Johanne의 솔루션의 완전한 컴파일 가능한 버전입니다 . 이것은 데비안 스퀴즈에서 g ++ 4.7의 스냅 샷으로 테스트되었습니다.
###################
johannes.cc
###################
#include <tuple>
#include <iostream>
using std::cout;
using std::endl;
template<int ...> struct seq {};
template<int N, int ...S> struct gens : gens<N-1, N-1, S...> {};
template<int ...S> struct gens<0, S...>{ typedef seq<S...> type; };
double foo(int x, float y, double z)
{
return x + y + z;
}
template <typename ...Args>
struct save_it_for_later
{
std::tuple<Args...> params;
double (*func)(Args...);
double delayed_dispatch()
{
return callFunc(typename gens<sizeof...(Args)>::type());
}
template<int ...S>
double callFunc(seq<S...>)
{
return func(std::get<S>(params) ...);
}
};
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-parameter"
#pragma GCC diagnostic ignored "-Wunused-variable"
#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
int main(void)
{
gens<10> g;
gens<10>::type s;
std::tuple<int, float, double> t = std::make_tuple(1, 1.2, 5);
save_it_for_later<int,float, double> saved = {t, foo};
cout << saved.delayed_dispatch() << endl;
}
#pragma GCC diagnostic pop
다음 SConstruct 파일을 사용할 수 있습니다
#####################
SConstruct
#####################
#!/usr/bin/python
env = Environment(CXX="g++-4.7", CXXFLAGS="-Wall -Werror -g -O3 -std=c++11")
env.Program(target="johannes", source=["johannes.cc"])
내 컴퓨터에서 이것은
g++-4.7 -o johannes.o -c -Wall -Werror -g -O3 -std=c++11 johannes.cc
g++-4.7 -o johannes johannes.o
다음은 C ++ 14 솔루션입니다.
template <typename ...Args>
struct save_it_for_later
{
std::tuple<Args...> params;
void (*func)(Args...);
template<std::size_t ...I>
void call_func(std::index_sequence<I...>)
{ func(std::get<I>(params)...); }
void delayed_dispatch()
{ call_func(std::index_sequence_for<Args...>{}); }
};
이것은 여전히 하나의 도우미 기능 ( call_func
) 이 필요합니다 . 이것은 일반적인 관용구이므로 표준은 std::call
가능한 구현과 마찬가지로 직접 지원해야 합니다.
// helper class
template<typename R, template<typename...> class Params, typename... Args, std::size_t... I>
R call_helper(std::function<R(Args...)> const&func, Params<Args...> const¶ms, std::index_sequence<I...>)
{ return func(std::get<I>(params)...); }
// "return func(params...)"
template<typename R, template<typename...> class Params, typename... Args>
R call(std::function<R(Args...)> const&func, Params<Args...> const¶ms)
{ return call_helper(func,params,std::index_sequence_for<Args...>{}); }
그러면 지연 파견이됩니다
template <typename ...Args>
struct save_it_for_later
{
std::tuple<Args...> params;
std::function<void(Args...)> func;
void delayed_dispatch()
{ std::call(func,params); }
};
(가능하더라도) 달성하기가 다소 복잡합니다. 이것이 이미 구현 된 라이브러리, 즉 Boost.Fusion ( 호출 함수)을 사용하는 것이 좋습니다. 보너스로 Boost Fusion은 C ++ 03 컴파일러와도 작동합니다.
C ++ 14 솔루션. 먼저, 유틸리티 보일러 플레이트 :
template<std::size_t...Is>
auto index_over(std::index_sequence<Is...>){
return [](auto&&f)->decltype(auto){
return decltype(f)(f)( std::integral_constant<std::size_t, Is>{}... );
};
}
template<std::size_t N>
auto index_upto(std::integral_constant<std::size_t, N> ={}){
return index_over( std::make_index_sequence<N>{} );
}
이를 통해 일련의 컴파일 타임 정수로 람다를 호출 할 수 있습니다.
void delayed_dispatch() {
auto indexer = index_upto<sizeof...(Args)>();
indexer([&](auto...Is){
func(std::get<Is>(params)...);
});
}
그리고 우리는 끝났습니다.
index_upto
그리고 index_over
새 외부 과부하를 생성하지 않고도 매개 변수 팩으로 작동 할 수 있습니다.
물론,에 17 C ++ 방금
void delayed_dispatch() {
std::apply( func, params );
}
이제 우리가 그것을 좋아한다면 c ++ 14 에서 다음과 같이 쓸 수 있습니다 :
namespace notstd {
template<class T>
constexpr auto tuple_size_v = std::tuple_size<T>::value;
template<class F, class Tuple>
decltype(auto) apply( F&& f, Tuple&& tup ) {
auto indexer = index_upto<
tuple_size_v<std::remove_reference_t<Tuple>>
>();
return indexer(
[&](auto...Is)->decltype(auto) {
return std::forward<F>(f)(
std::get<Is>(std::forward<Tuple>(tup))...
);
}
);
}
}
비교적 쉬운 c ++ 17 구문을 제공 할 수 있습니다.
void delayed_dispatch() {
notstd::apply( func, params );
}
다만 교체 notstd
와 std
컴파일러 업그레이드 및 밥 삼촌 때.
동일한 문제를 해결하는 다른 방법을 찾았을 때 주어진 대답에 따라 문제에 대해 더 생각하면 다음과 같습니다.
template <int N, int M, typename D>
struct call_or_recurse;
template <typename ...Types>
struct dispatcher {
template <typename F, typename ...Args>
static void impl(F f, const std::tuple<Types...>& params, Args... args) {
call_or_recurse<sizeof...(Args), sizeof...(Types), dispatcher<Types...> >::call(f, params, args...);
}
};
template <int N, int M, typename D>
struct call_or_recurse {
// recurse again
template <typename F, typename T, typename ...Args>
static void call(F f, const T& t, Args... args) {
D::template impl(f, t, std::get<M-(N+1)>(t), args...);
}
};
template <int N, typename D>
struct call_or_recurse<N,N,D> {
// do the call
template <typename F, typename T, typename ...Args>
static void call(F f, const T&, Args... args) {
f(args...);
}
};
구현을 다음으로 변경해야 delayed_dispatch()
합니다.
void delayed_dispatch() {
dispatcher<Args...>::impl(func, params);
}
이것은 std::tuple
자체적으로 매개 변수 팩으로 재귀 적으로 변환하여 작동합니다 . call_or_recurse
완료된 매개 변수 팩의 압축을 풀기 만하는 실제 호출로 재귀를 종료하기위한 전문화로 필요합니다.
나는 이것이 어쨌든 "더 나은"해결책인지 확신하지 못하지만, 그것을 생각하고 해결하는 또 다른 방법입니다.
다른 대안 솔루션 enable_if
으로을 사용 하여 이전 솔루션보다 더 간단한 것을 형성 할 수 있습니다 .
#include <iostream>
#include <functional>
#include <tuple>
void f(int a, double b, void* c) {
std::cout << a << ":" << b << ":" << c << std::endl;
}
template <typename ...Args>
struct save_it_for_later {
std::tuple<Args...> params;
void (*func)(Args...);
template <typename ...Actual>
typename std::enable_if<sizeof...(Actual) != sizeof...(Args)>::type
delayed_dispatch(Actual&& ...a) {
delayed_dispatch(std::forward<Actual>(a)..., std::get<sizeof...(Actual)>(params));
}
void delayed_dispatch(Args ...args) {
func(args...);
}
};
int main() {
int a=666;
double b = -1.234;
void *c = NULL;
save_it_for_later<int,double,void*> saved = {
std::tuple<int,double,void*>(a,b,c), f};
saved.delayed_dispatch();
}
첫 번째 과부하는 튜플에서 하나 이상의 인수를 취하여 매개 변수 팩에 넣습니다. 두 번째 과부하는 일치하는 매개 변수 팩을 가져 와서 실제 호출을 수행합니다. 첫 번째 과부하는 두 번째를 실행할 수있는 경우에만 비활성화됩니다.
C ++ 14 std :: index_sequence (및 템플릿 매개 변수 RetT로 함수 반환 유형)를 사용하여 Johannes의 솔루션 변형 :
template <typename RetT, typename ...Args>
struct save_it_for_later
{
RetT (*func)(Args...);
std::tuple<Args...> params;
save_it_for_later(RetT (*f)(Args...), std::tuple<Args...> par) : func { f }, params { par } {}
RetT delayed_dispatch()
{
return callFunc(std::index_sequence_for<Args...>{});
}
template<std::size_t... Is>
RetT callFunc(std::index_sequence<Is...>)
{
return func(std::get<Is>(params) ...);
}
};
double foo(int x, float y, double z)
{
return x + y + z;
}
int testTuple(void)
{
std::tuple<int, float, double> t = std::make_tuple(1, 1.2, 5);
save_it_for_later<double, int, float, double> saved (&foo, t);
cout << saved.delayed_dispatch() << endl;
return 0;
}
참고 URL : https://stackoverflow.com/questions/7858817/unpacking-a-tuple-to-call-a-matching-function-pointer
'Programing' 카테고리의 다른 글
Android에서 원형 ImageView를 만드는 방법은 무엇입니까? (0) | 2020.04.05 |
---|---|
HTML 양식의 여러 제출 버튼 (0) | 2020.04.05 |
SortedList와 SortedDictionary의 차이점은 무엇입니까? (0) | 2020.04.05 |
파이썬 들여 쓰기를 수정하는 방법 (0) | 2020.04.05 |
풀 : apply, apply_async 또는 map을 언제 사용합니까? (0) | 2020.04.05 |