एक मिलान फ़ंक्शन पॉइंटर को कॉल करने के लिए एक टपल को "अनपैकिंग"


253

मैं 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, या भंडारण / कुछ मूल्यों और एक समारोह सूचक अग्रेषण एक मनमाना भविष्य बिंदु तक का एक ही शुद्ध परिणाम प्राप्त करने के लिए एक विकल्प बेहतर तरीका?


5
आप सिर्फ उपयोग क्यों नहीं कर सकते auto saved = std::bind(f, a, b, c);... फिर बाद में बस कॉल करें saved()?
चार्ल्स साल्विया

हमेशा मेरे इंटरफ़ेस को नियंत्रित करने के लिए नहीं। मैं किसी और से अनुबंध करके एक टुपल प्राप्त करता हूं और बाद में इसके साथ काम करना चाहता हूं।
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
वाह, मुझे नहीं पता था कि अनपैकिंग ऑपरेटर का उपयोग किया जा सकता है, यह अच्छा है!
ल्यूक टोराइल

5
जोहान्स, मुझे यह एहसास हुआ कि आपको यह पोस्ट करने के बाद 2+ साल हो गए हैं, लेकिन एक चीज जो मैं संघर्ष कर रहा हूं वह है struct gensजेनेरिक डेफिनिशन (वह जो उसी की एक विस्तारित व्युत्पत्ति से विरासत में मिली है )। मैं देखता हूं कि यह अंत में विशेषज्ञता के साथ हिट करता है। अगर मूड आपको सूट करता है और आपके पास अतिरिक्त चक्र हैं, यदि आप उस पर विस्तार कर सकते हैं, और इसके लिए इसका उपयोग कैसे किया जाता है, तो मैं सदा आभारी रहूंगा। और मेरी इच्छा है कि मैं इसे सौ बार वोट कर सकूं। मुझे इस कोड से स्पर्शरेखा के साथ खेलने में अधिक मज़ा आया है। धन्यवाद।
WhozCraig

22
@WhozCraig: यह जो करता है वह एक प्रकार उत्पन्न करता है 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>। बहुत चालाक चाल।
दिमागी वायरस

2
@NirFriedman: ज़रूर, बस के विशिष्ट संस्करण को प्रतिस्थापित करें gens:template <int N, int... S> struct gens { typedef typename gens<N-1, N-1, S...>::type type; };
marton78

11
यह वाल्टर के उत्तर और उसके बाद की टिप्पणियों की गूंज के लायक है: लोक को अब अपने स्वयं के पहियों का आविष्कार करने की आवश्यकता नहीं है। एक अनुक्रम उत्पन्न करना इतना सामान्य था कि इसे C ++ 14 में मानकीकृत किया गया था std::integer_sequence<T, N>और इसके लिए विशेषज्ञता std::size_t, std::index_sequence<N>- साथ ही उनके संबद्ध सहायक कार्य std::make_in(teger|dex)_sequence<>()और std::index_sequence_for<Ts...>()। और सी ++ 17 में पुस्तकालय में एकीकृत कई अन्य अच्छी चीजें हैं - विशेष रूप से शामिल हैं std::applyऔर std::make_from_tuple, जो अनपैकिंग और कॉलिंग बिट्स को संभालेंगी
अंडरस्कोर_ड

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 समाधान अभी भी गायब है। संपादित करें: नहीं, यह वास्तव में वाल्टर के उत्तर में है।

यह समारोह दिया गया है:

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: क्या आप कुछ इस तरह से यहाँ प्राप्त करना चाहते हैं ?
दाविदघ

धन्यवाद, मेरे 2 सवाल हैं: 1. मैं std::make_uniqueसीधे पास क्यों नहीं कर सकता ? क्या इसे ठोस कार्य उदाहरण की आवश्यकता है? 2. क्यों std::move(ts)...हम बदल सकते हैं [](auto... ts)करने के लिए [](auto&&... ts)?
Xeverous

@Xeverous: 1. हस्ताक्षरों से काम नहीं करता है: आपकी std::make_uniqueउम्मीद है कि एक ट्यूपल है, और एक ट्यूपल को एक अनप्लग्ड ट्यूपल से केवल दूसरे कॉल के माध्यम से बनाया जा सकता है std::make_tuple। यह वही है जो मैंने लैम्ब्डा में किया है (हालांकि यह बहुत बेमानी है, क्योंकि आप टपल को केवल यूनिक पॉइंटर में बिना किसी उपयोग के कॉपी कर सकते हैं call)।
18

1
अब यह होना चाहिए जवाब।
फ्यूरिश

44

यह ऑकलैंड के प्रश्न के लिए जोहान्स के समाधान का एक पूर्ण संकलन योग्य संस्करण है , इस उम्मीद में कि यह किसी के लिए उपयोगी हो सकता है। यह डेबियन निचोड़ पर जी ++ 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

आपको चर और छ की आवश्यकता क्यों है?
शौश

@ संतोष मुझे लगता है कि उन्हें ज़रूरत नहीं है। मैं भूल जाता हूं कि मैंने उन्हें क्यों जोड़ा; लगभग तीन साल हो गए। लेकिन मुझे लगता है, यह दिखाने के लिए कि तात्कालिकता काम करती है।
फहीम मीठा

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_sequenceऔर index_sequenceहेल्पर प्रकारों को यहां समझाया गया है: en.cppreference.com/w/cpp/utility/integer_fterence की विशिष्ट अनुपस्थिति को नोटिस करें std::make_index_sequence(Args...), यही वजह है कि वाल्टर को क्लंकल सिंटैक्स में मजबूर किया गया था std::index_sequence_for<Args...>{}
क्क्सप्लसोन

3
और जाहिरा तौर पर सी ++ 17 में 3/2016 के बाद से मतदान किया गया :: std :: apply (func, tup): en.cppreference.com/w/cpp/utility/apply
ddevienne

18

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


6

उपाय। सबसे पहले, कुछ उपयोगिता बॉयलरप्लेट:

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

बस की जगह notstdके साथ stdजब अपने संकलक उन्नयन और बॉब अपने चाचा है।


std::apply<- मेरी कानों में संगीत
Flexo

@ फेलेक्सो केवल कम से कम थोड़ा index_uptoलचीला है। ;) के funcसाथ index_uptoऔर पीछे std::applyक्रमशः तर्कों के साथ कॉल करने का प्रयास करें । जाहिर है, जो एक टखने पीछे से एक समारोह आह्वान करना चाहता है।
यक्क - एडम नेवरामोंट

मामूली बिंदु: std::tuple_size_vC ++ 17 है, इसलिए C ++ 14 समाधान के लिए जिसे प्रतिस्थापित करना होगाtypename std::tuple_size<foo>::value
basteln

@basteln मुझे आशा valueहै कि एक प्रकार नहीं है। लेकिन किसी भी तरह तय किया।
यक्क - एडम नेवरामोंट

@ यक नो, इट sizeof...(Types)। मुझे आपके समाधान के बिना पसंद है typename
बस्टेलन

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
मैं कुछ समय पहले इसी के समान कुछ काम करता था। अगर मेरे पास समय है तो मैं दूसरी बार देखूंगा और देखूंगा कि यह वर्तमान उत्तरों की तुलना कैसे करता है।
माइकल प्राइस

@MichaelPrice - विशुद्ध रूप से सीखने के नजरिए से मैं ऐसे किसी भी वैकल्पिक समाधान को देखने में दिलचस्पी रखूँगा जो स्टैक पॉइंटर (या इसी तरह से कन्वेंशन विशिष्ट ट्रिक्स को कॉल करना) को कुछ भयानक हैक करने के लिए उबाल नहीं देता है।
फ्लेक्सो

2

जोहान्स के समाधान की मेरी भिन्नता C ++ 14 std :: index_fterence (और फ़ंक्शन रिटर्न प्रकार टेम्पलेट पैरामीटर RetT के रूप में) का उपयोग करके:

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 सबसे पहले, टेम्पलेट जटिलता के संदर्भ में, यह कुछ भी नहीं है । दूसरे, ज्यादातर हेल्पर्स टेम्प्लेट एक टन समय के लिए बचाए गए प्रारंभिक निवेश होते हैं, जब उन्हें बाद में इंस्टेंटेट किया जाता है। अन्त में, क्या, क्या आप नहीं कर सकते हैं करने की क्षमता क्या टेम्पलेट्स आप करने के लिए अनुमति देते हैं? आप बस इसका उपयोग नहीं कर सकते हैं, और अप्रासंगिक टिप्पणियों को नहीं छोड़ सकते हैं जो अन्य प्रोग्रामर को पुलिसिंग करते हैं।
अंडरस्कोर_ड
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.