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


254

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::tuplevariadic 템플릿 과 관련된 문제 template <typename Head, typename ...Tail>의 경우 모든 유형을 하나씩 재귀 적으로 평가하는 것과 같은 다른 템플릿을 작성 하지만 함수 호출을 전달하기 위해 수행하는 방법을 볼 수는 없습니다.

이것에 대한 진정한 동기는 다소 복잡하며 어쨌든 대부분 학습 운동입니다. 다른 인터페이스와의 계약에 의해 튜플을 건네 주었으므로 변경할 수는 없지만 함수 호출로 압축을 풀고 싶은 욕구는 내 것입니다. 이는 std::bind근본적인 문제를 회피하기위한 저렴한 방법으로 사용 하지 않습니다.

을 사용하여 호출을 전달하는 깔끔한 방법 std::tuple또는 임의의 미래 시점까지 일부 값과 함수 포인터를 저장 / 전달하는 동일한 결과를 얻는 더 좋은 방법은 무엇입니까?


5
왜 그냥 사용할 수 없어요 ? auto saved = std::bind(f, a, b, c);나중에 전화 saved()하세요?
Charles Salvia

항상 제어 할 인터페이스가 아닙니다. 나는 다른 사람으로부터 계약에 의해 튜플을 받고 그 후에 그 일을하고 싶습니다.
Flexo

답변:


275

당신은 숫자의 매개 변수 팩을 작성하고 포장을 풀어야합니다

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

4
와우, 나는 포장 풀기 연산자가 그렇게 사용될 수 있다는 것을 몰랐다.
Luc Touraille

5
Johannes, 나는 당신이 이것을 게시 한 지 2 년이 지난 것을 알고 있지만, 내가 고투하고있는 한 가지는 struct gens일반적인 정의 (상기의 확장 된 파생 에서 상속받은 것 )입니다. 나는 그것이 0으로 전문화되는 것을 본다. 기분이 당신에게 적합하고 당신은 여분의 사이클을 가지고 있다면, 그것을 확장 할 수 있다면, 그것이 어떻게 활용되는지, 나는 영원히 감사 할 것입니다. 그리고 나는 이것을 백 번 투표 할 수 있기를 바랍니다. 이 코드에서 탄젠트를 가지고 노는 것이 더 재미있었습니다. 감사.
WhozCraig

22
@WhozCraig : 그것이하는 것은 type을 생성하는 것입니다 seq<0, 1, .., N-1>. 작동 방식 : gens<5>: gens<4, 4>: gens<3, 3, 4>: gens<2, 2, 3, 4> : gens<1, 1, 2, 3, 4> : gens<0, 0, 1, 2, 3, 4>. 마지막 유형은 전문화되어 작성 seq<0, 1, 2, 3, 4>합니다. 꽤 영리한 속임수입니다.
mindvirus

2
@NirFriedman : 물론, 특수화되지 않은 버전으로 대체 gens:template <int N, int... S> struct gens { typedef typename gens<N-1, N-1, S...>::type type; };
marton78

11
월터의 대답과 그에 대한 의견을 반영 할 가치가 있습니다. 사람들은 더 이상 자신의 바퀴를 발명 할 필요가 없습니다. 시퀀스를 생성하는 것은으로 14 ++ C에서 표준화 된 너무 많았다 std::integer_sequence<T, N>을 위해 그와 전문화 std::size_t, std::index_sequence<N>플러스 관련 도우미 기능 - std::make_in(teger|dex)_sequence<>()std::index_sequence_for<Ts...>(). 특히 포함 - 그리고 C ++에서 17 라이브러리에 통합 다른 좋은 일들이 많이있다 std::applystd::make_from_tuple포장 해체 및 호출 비트를 처리 할 것이다,
underscore_d

61

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

데모


이 데모를 스마트 포인터와 함께 사용할 수 없습니다. http://coliru.stacked-crooked.com/a/8ea8bcc878efc3cb
Xeverous

@ Xeverous : 여기에 이런 것을 원 하십니까?
davidhigh

감사합니다. 2 가지 질문이 있습니다. 1. std::make_unique직접 통과 할 수없는 이유는 무엇 입니까? 구체적인 함수 인스턴스가 필요합니까? 2. 왜 std::move(ts)...우리가로 바꿀 [](auto... ts)[](auto&&... ts)있습니까?
Xeverous

@Xeverous : 1. 서명에서 작동하지 않습니다 std::make_unique. 튜플 이 필요하고을 ( 를) 다른 호출을 통해서만 압축 해제 된 튜플에서 만들 수 있습니다 std::make_tuple. 이것은 내가 람다에서 한 일입니다 (이 중복은 매우 중요하지만 튜플을 사용하지 않고 고유 포인터로 복사 할 수 있기 때문에 call).
davidhigh

1
이것은 지금해야 대답.
Fureeish

44

이것은 awoodland의 질문에 대한 Johannes의 솔루션 의 완전한 컴파일 가능한 버전입니다 . 누군가에게 유용 할 수 있기를 바랍니다. 이것은 데비안 스퀴즈에서 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

왜 변수 s와 g가 필요합니까?
shoosh

@ shoosh 나는 그들이 필요하지 않은 것 같아요. 내가 왜 그것들을 추가했는지 잊습니다. 거의 3 년이 지났습니다. 그러나 인스턴스화가 작동한다는 것을 보여주기 위해 생각합니다.
Faheem Mitha

42

다음은 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); }
};

8
의 (제안 된) 구현을 위해 찬성했습니다 std::call. C ++ 14의 혼란스러운 동물원 integer_sequenceindex_sequence도우미 유형은 여기에 설명되어 있습니다. en.cppreference.com/w/cpp/utility/integer_sequence 공지 사항을의 눈에 띄는없는 std::make_index_sequence(Args...)월터는 clunkier 구문을 강요 한 이유입니다 std::index_sequence_for<Args...>{}.
Quuxplusone

3
그리고 2016 년 3 월 이후 std :: apply (func, tup)으로 C ++ 17에 투표 한 것으로 보입니다. en.cppreference.com/w/cpp/utility/apply
ddevienne

18

(가능하더라도) 달성하기가 다소 복잡합니다. 이것이 이미 구현 된 라이브러리, 즉 Boost.Fusion ( 호출 기능)을 사용하는 것이 좋습니다. 보너스로 Boost Fusion은 C ++ 03 컴파일러와도 작동합니다.


7

해결책. 먼저, 유틸리티 보일러 플레이트 :

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컴파일러 업그레이드 및 밥 삼촌 때.


std::apply<-내 귀에 음악
Flexo

@Flexo 약간 짧고 index_upto유연하지 않습니다. ;)를 호출 시도 func뒤쪽과 인수 index_uptostd::apply각각. 분명히, 도대체 누가 튜플에서 뒤로 함수를 호출하려고합니다.
Yakk-Adam Nevraumont

사소한 점 : std::tuple_size_vC ++ 17이므로 C ++ 14 솔루션으로 대체해야합니다.typename std::tuple_size<foo>::value
basteln

@ basteln value유형이 아니길 바랍니다 . 그러나 어쨌든 수정되었습니다.
Yakk-Adam Nevraumont

@ 야크 아니야 sizeof...(Types). 나는없이 귀하의 솔루션을 좋아합니다 typename.
basteln

3

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

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

첫 번째 과부하는 튜플에서 하나 이상의 인수를 취하여 매개 변수 팩에 넣습니다. 두 번째 과부하는 일치하는 매개 변수 팩을 가져 와서 실제 호출을 수행합니다. 첫 번째 과부하는 두 번째를 실행할 수있는 경우에만 비활성화됩니다.


1
나는 이것과 끔찍하게 비슷한 일을했습니다. 시간이 있으면 다시 한 번 살펴보고 현재 답변과 어떻게 비교되는지 보겠습니다.
Michael Price

@MichaelPrice-순전히 학습의 관점에서 스택 포인터를 botching하는 끔찍한 해킹 (또는 유사하게 컨벤션 특정 트릭을 호출)으로 대체되지 않는 대체 솔루션을 보는 데 관심이 있습니다.
Flexo

2

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

이러한 모든 솔루션이 초기 문제를 해결할 수는 있지만 솔직히 말해서이 템플릿은 단순성 및 유지 관리 성 측면에서 잘못된 방향으로 진행되지 않습니까?
xy

C ++ 11 및 14에서 템플릿이 훨씬 더 잘 이해되고 이해 될 수 있다고 생각합니다. 좋은 템플릿을 개발하는 것이 단순히 템플릿을 사용하는 것보다 훨씬 어렵다는 것에 동의합니다.
schwart

1
@xy 첫째, 템플릿 복잡성 측면에서 이것은 아무것도 아닙니다 . 둘째, 대부분의 도우미 템플릿은 나중에 인스턴스화 할 때 절약되는 톤에 대한 초기 투자입니다. 마지막으로, 무엇을, 당신은 오히려 것 없는 당신이 할 수 있도록 무엇을 템플릿 할 수있는 능력을 가지고? 당신은 그것을 사용할 수 없었고 다른 프로그래머를 치안하는 것처럼 보이는 관련없는 의견을 남기지 않았습니다.
underscore_d
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.