वेरिएडिक टेम्प्लेट तर्कों को कैसे स्टोर करें?


89

क्या बाद में उपयोग के लिए किसी पैरामीटर पैक को स्टोर करना संभव है?

template <typename... T>
class Action {
private:        
    std::function<void(T...)> f;
    T... args;  // <--- something like this
public:
    Action(std::function<void(T...)> f, T... args) : f(f), args(args) {}
    void act(){
        f(args);  // <--- such that this will be possible
    }
}

फिर बाद में:

void main(){
    Action<int,int> add([](int x, int y){std::cout << (x+y);}, 3, 4);

    //...

    add.act();
}

3
हाँ; एक ट्यूपल को स्टोर करें और कॉल करने के लिए किसी प्रकार के इंडेक्स पैक का उपयोग करें।
केरेक एसबी

क्या इससे आपके सवाल का जवाब मिलता है? C ++ एक पैरामीटर पैक को चर के रूप में कैसे स्टोर करें
user202729

जवाबों:


67

आप यहां क्या करना चाहते हैं, इसे पूरा करने के लिए, आपको अपने टेम्प्लेट के तर्कों को टूप्ले में संग्रहीत करना होगा:

std::tuple<Ts...> args;

इसके अलावा, आपको अपने निर्माता को थोड़ा बदलना होगा। विशेष रूप से, आपकी पैरामीटर सूची में सार्वभौमिक संदर्भों की अनुमति देने के argsसाथ आरंभ करना std::make_tuple:

template <typename F, typename... Args>
Action(F&& func, Args&&... args)
    : f(std::forward<F>(func)),
      args(std::forward<Args>(args)...)
{}

इसके अलावा, आपको इस तरह एक अनुक्रम जनरेटर स्थापित करना होगा:

namespace helper
{
    template <int... Is>
    struct index {};

    template <int N, int... Is>
    struct gen_seq : gen_seq<N - 1, N - 1, Is...> {};

    template <int... Is>
    struct gen_seq<0, Is...> : index<Is...> {};
}

और आप इस तरह के जनरेटर लेने के संदर्भ में अपनी विधि को लागू कर सकते हैं:

template <typename... Args, int... Is>
void func(std::tuple<Args...>& tup, helper::index<Is...>)
{
    f(std::get<Is>(tup)...);
}

template <typename... Args>
void func(std::tuple<Args...>& tup)
{
    func(tup, helper::gen_seq<sizeof...(Args)>{});
}

void act()
{
   func(args);
}

और वह यह! तो अब आपकी कक्षा इस तरह दिखनी चाहिए:

template <typename... Ts>
class Action
{
private:
    std::function<void (Ts...)> f;
    std::tuple<Ts...> args;
public:
    template <typename F, typename... Args>
    Action(F&& func, Args&&... args)
        : f(std::forward<F>(func)),
          args(std::forward<Args>(args)...)
    {}

    template <typename... Args, int... Is>
    void func(std::tuple<Args...>& tup, helper::index<Is...>)
    {
        f(std::get<Is>(tup)...);
    }

    template <typename... Args>
    void func(std::tuple<Args...>& tup)
    {
        func(tup, helper::gen_seq<sizeof...(Args)>{});
    }

    void act()
    {
        func(args);
    }
};

यहाँ पर आपका पूरा कार्यक्रम कोलिरु पर है।


अद्यतन: यहाँ एक सहायक विधि है जिसके द्वारा टेम्पलेट तर्कों के विनिर्देश आवश्यक नहीं हैं:

template <typename F, typename... Args>
Action<Args...> make_action(F&& f, Args&&... args)
{
    return Action<Args...>(std::forward<F>(f), std::forward<Args>(args)...);
}

int main()
{
    auto add = make_action([] (int a, int b) { std::cout << a + b; }, 2, 3);

    add.act();
}

और फिर, यहाँ एक और डेमो है।


1
क्या आप अपने उत्तर में थोड़ा विस्तार कर सकते हैं?
एरिक बी

1
चूंकि Ts ... एक क्लास टेम्प्लेट पैरामीटर है, फ़ंक्शन टेम्पलेट पैरामीटर नहीं, Ts && ... सार्वभौमिक संदर्भों के एक पैकेट को परिभाषित नहीं करता है क्योंकि पैरामीटर पैक के लिए कोई प्रकार की कटौती नहीं होती है। @ जोगोजपन यह सुनिश्चित करने का सही तरीका दिखाता है कि आप निर्माणकर्ता के लिए सार्वभौमिक संदर्भ पारित कर सकते हैं।
मास्सर्टिस

3
संदर्भ और वस्तु जीवनकाल के लिए बाहर देखो! void print(const std::string&); std::string hello(); auto act = make_action(print, hello());अच्छा नहीं है। मैं उस व्यवहार को प्राथमिकता दूंगा std::bind, जो प्रत्येक तर्क की एक प्रति बनाता है जब तक कि आप उस std::refया के साथ अक्षम न करें std::cref
aschepler

1
मुझे लगता है कि @ जोगोजपन का एक तरीका अधिक संक्षिप्त और पठनीय समाधान है।
टिम कुइपर्स

1
@Riddick बदलें args(std::make_tuple(std::forward<Args>(args)...))करने के लिए args(std::forward<Args>(args)...)। BTW मैंने यह एक लंबे समय से पहले लिखा था और मैं कुछ तर्कों के लिए एक फ़ंक्शन को बांधने के उद्देश्य से इस कोड का उपयोग नहीं करूंगा। मैं बस std::invoke()या std::apply()आजकल का उपयोग करता हूं ।
0x499602D2

23

आप इसके लिए उपयोग कर सकते हैं std::bind(f,args...)। यह एक चल और संभवतः प्रतिलिपि बनाने योग्य ऑब्जेक्ट उत्पन्न करेगा जो फ़ंक्शन ऑब्जेक्ट की एक प्रति और बाद के उपयोग के लिए प्रत्येक तर्क को संग्रहीत करता है:

#include <iostream>
#include <utility>
#include <functional>

template <typename... T>
class Action {
public:

  using bind_type = decltype(std::bind(std::declval<std::function<void(T...)>>(),std::declval<T>()...));

  template <typename... ConstrT>
  Action(std::function<void(T...)> f, ConstrT&&... args)
    : bind_(f,std::forward<ConstrT>(args)...)
  { }

  void act()
  { bind_(); }

private:
  bind_type bind_;
};

int main()
{
  Action<int,int> add([](int x, int y)
                      { std::cout << (x+y) << std::endl; },
                      3, 4);

  add.act();
  return 0;
}

ध्यान दें कि std::bindएक फ़ंक्शन है और आपको डेटा सदस्य के रूप में संग्रहीत करने की आवश्यकता है, इसे कॉल करने का परिणाम है। उस परिणाम का डेटा प्रकार भविष्यवाणी करना आसान नहीं है (मानक इसे ठीक भी निर्दिष्ट नहीं करता है), इसलिए मैं संकलन समय पर उस डेटा प्रकार के संयोजन decltypeऔर उपयोग करने के लिए उपयोग करता हूं std::declval। ऊपर की परिभाषा देखें Action::bind_type

यह भी ध्यान दें कि मैंने अस्थायी निर्माणकर्ता में सार्वभौमिक संदर्भों का उपयोग कैसे किया। यह सुनिश्चित करता है कि आप उन तर्कों को पारित कर सकते हैं जो कक्षा के टेम्पलेट मापदंडों से T...बिल्कुल मेल नहीं खाते हैं (जैसे आप कुछ के लिए संदर्भों का उपयोग कर सकते हैं Tऔर आप उन्हें bindकॉल के लिए आगे भेज देंगे ।)

अंतिम नोट: यदि आप तर्कों को संदर्भ के रूप में संग्रहीत करना चाहते हैं (ताकि आपके द्वारा पारित फ़ंक्शन को संशोधित कर सकें, केवल उपयोग के बजाय, उन्हें), तो आपको std::refउन्हें संदर्भ ऑब्जेक्ट में लपेटने के लिए उपयोग करने की आवश्यकता है । केवल पास होने T &से मान की एक प्रति बन जाएगी, संदर्भ नहीं।

कोलीरू पर परिचालन कोड


क्या प्रतिद्वंद्वियों को बांधना खतरनाक नहीं है? क्या उन को अमान्य नहीं addकिया जाएगा जब एक अलग दायरे में परिभाषित किया act()जाता है तो कहा जाता है? क्या ConstrT&... argsइसके बजाय निर्माणकर्ता को नहीं मिलना चाहिए ConstrT&&... args?
टिम कुइपर्स

1
@Angelorf मेरे देर से जवाब के लिए क्षमा करें। आप कॉल करने के लिए rvalues ​​मतलब है bind()? चूंकि bind()प्रतियां बनाने की गारंटी है (या हौसले से बनाई गई वस्तुओं में स्थानांतरित करने के लिए), मुझे नहीं लगता कि कोई समस्या हो सकती है।
जोगोजपन

@ जोगोजपन क्विक नोट, MSVC17 को कंस्ट्रक्टर में फंक्शन को bind_ के साथ फॉरवर्ड करने की आवश्यकता है (यानी bind_ (std :: फॉरवर्ड <std :: function <void (T ...) >> (f), std - फॉरवर्ड < ConstrT> ((args) ...)
Outshined

1
इनिशियलाइज़र में, bind_(f, std::forward<ConstrT>(args)...)मानक के अनुसार अपरिभाषित व्यवहार है, क्योंकि वह कंस्ट्रक्टर कार्यान्वयन-परिभाषित है। bind_typeहालांकि कॉपी और / या मूव-कंस्ट्रक्टिव होना निर्दिष्ट है, फिर bind_{std::bind(f, std::forward<ConstrT>(args)...)}भी काम करना चाहिए।
joshtch

4

यह सवाल C ++ 11 दिनों का था। लेकिन अब खोज परिणामों में इसे खोजने वालों के लिए कुछ अपडेट:

एक std::tupleसदस्य अभी भी आम तौर पर तर्कों को संग्रहीत करने का सीधा तरीका है। ( @ जोगोजपन केstd::bind समान एक समाधान भी काम करेगा यदि आप किसी विशिष्ट फ़ंक्शन को कॉल करना चाहते हैं, लेकिन यदि आप अन्य तरीकों से तर्कों को एक्सेस करना चाहते हैं, या एक से अधिक फ़ंक्शन के लिए तर्कों को पास करना चाहते हैं, तो नहीं)

C ++ 14 और बाद में, std::make_index_sequence<N>या std::index_sequence_for<Pack...>0x499602D2 के समाधानhelper::gen_seq<N> में देखे गए टूल को बदल सकते हैं :

#include <utility>

template <typename... Ts>
class Action
{
    // ...
    template <typename... Args, std::size_t... Is>
    void func(std::tuple<Args...>& tup, std::index_sequence<Is...>)
    {
        f(std::get<Is>(tup)...);
    }

    template <typename... Args>
    void func(std::tuple<Args...>& tup)
    {
        func(tup, std::index_sequence_for<Args...>{});
    }
    // ...
};

C ++ 17 और बाद में, std::applyका उपयोग टपल को खोलने के लिए किया जा सकता है:

template <typename... Ts>
class Action
{
    // ...
    void act() {
        std::apply(f, args);
    }
};

यहां सरलीकृत कार्यान्वयन दिखाते हुए एक पूर्ण C ++ 17 कार्यक्रम है। मैंने make_actionसंदर्भ प्रकारों से बचने के लिए भी अद्यतन किया tuple, जो हमेशा तर्क वितर्क के लिए बुरा था और लवल्यू तर्कों के लिए काफी जोखिम भरा था।


3

मुझे लगता है कि आपको एक XY समस्या है। जब आप सिर्फ कॉलबैंटे पर एक लैम्ब्डा का उपयोग कर सकते हैं तो पैरामीटर पैक को स्टोर करने के लिए सभी परेशानी क्यों जाएं? अर्थात,

#include <functional>
#include <iostream>

typedef std::function<void()> Action;

void callback(int n, const char* s) {
    std::cout << s << ": " << n << '\n';
}

int main() {
    Action a{[]{callback(13, "foo");}};
    a();
}

क्योंकि मेरे आवेदन में, एक्शन में वास्तव में 3 अलग-अलग फ़ंक्शनलर्स हैं जो सभी संबंधित हैं, और मैं ऐसी कक्षाएं लगाऊंगा जिनमें यह 1 एक्शन हो, न कि 3 एसटी :: फ़ंक्शन
एरिक बी
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.