Programing

일치하는 함수 포인터를 호출하기 위해 튜플 "풀기"

lottogame 2020. 4. 5. 19:52
반응형

일치하는 함수 포인터를 호출하기 위해 튜플 "풀기"


나는 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&params, 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&params)
{ 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 컴파일러와도 작동합니다.


솔루션. 먼저, 유틸리티 보일러 플레이트 :

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새 외부 과부하를 생성하지 않고도 매개 변수 팩으로 작동 할 수 있습니다.

물론,에 방금

void delayed_dispatch() {
  std::apply( func, params );
}

이제 우리가 그것을 좋아한다면 에서 다음과 같이 쓸 수 있습니다 :

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))...
        );
      }
    );
  }
}

비교적 쉬운 구문을 제공 할 수 있습니다.

void delayed_dispatch() {
  notstd::apply( func, params );
}

다만 교체 notstdstd컴파일러 업그레이드 및 밥 삼촌 때.


동일한 문제를 해결하는 다른 방법을 찾았을 때 주어진 대답에 따라 문제에 대해 더 생각하면 다음과 같습니다.

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

반응형