c++ - मिलान करने वाले फ़ंक्शन पॉइंटर को कॉल करने के लिए एक टुपल को "अनपॅकिंग" करें




function-pointers c++11 (6)

मैं एक 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 या variadic टेम्पलेट्स से जुड़ी समस्याओं के लिए मैं template <typename Head, typename ...Tail> जैसे सभी प्रकारों का एक-दूसरे का मूल्यांकन करने के लिए एक और टेम्पलेट 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) ...);
  }
// ...

दिए गए उत्तर के आधार पर कुछ और समस्या के बारे में सोचते हुए मुझे एक ही समस्या को हल करने का एक और तरीका मिला है:

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 को अपने पैरामीटर में पैरामीटर पैक में std::tuple परिवर्तित करके काम करता है। call_or_recurse को वास्तविक कॉल के साथ रिकर्सन को समाप्त करने के लिए विशेषज्ञता के रूप में आवश्यक है, जो कि पूर्ण पैरामीटर पैक को 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();
}

पहला अधिभार केवल ट्यूपल से एक और तर्क लेता है और इसे पैरामीटर पैक में रखता है। दूसरा अधिभार एक मिलान पैरामीटर पैक लेता है और फिर वास्तविक कॉल करता है, पहले ओवरलोड को एक और एकमात्र मामले में अक्षम किया जाता है जहां दूसरा व्यवहार्य होगा।


यह हासिल करने के लिए थोड़ा जटिल है (भले ही यह संभव है)। मैं आपको एक लाइब्रेरी का उपयोग करने की सलाह देता हूं जहां यह पहले ही कार्यान्वित किया गया है, अर्थात् Boost.Fusion ( invoke फ़ंक्शन)। बोनस के रूप में, बूस्ट फ़्यूज़न सी ++ 03 कंपाइलर्स के साथ भी काम करता है।


यहां एक सी ++ 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); }
};

सी ++ 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);

बस महसूस किया कि इस धागे में एक जवाब में एक बार कहा जाना चाहिए (यह पहले से ही टिप्पणियों में से एक में दिखाई दिया है)।

इस धागे में मूल सी ++ 14 समाधान अभी भी गायब है। संपादित करें: नहीं, यह वास्तव में वाल्टर के जवाब में है।

यह कार्य दिया गया है:

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

DEMO


सी ++ 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 में आप बस

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

अब, अगर हमें यह पसंद है, तो सी ++ 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))...
        );
      }
    );
  }
}

अपेक्षाकृत आसानी से और क्लीनर सी ++ 17 वाक्यविन्यास जहाज के लिए तैयार हो जाओ।

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

जब आपके कंपाइलर अपग्रेड और बॉब आपके चाचा हैं तो बस std साथ notstd को प्रतिस्थापित करें।







iterable-unpacking