C ++ 14 में Init कैप्चर के साथ C ++ लैंबडा कोड जनरेशन


9

मैं कोड कोड को समझने / स्पष्ट करने की कोशिश कर रहा हूं जो कि कैप्चर लैम्बदास को विशेष रूप से C ++ 14 में जोड़े गए सामान्यीकृत कैद कैप्चर में पारित किया जाता है।

नीचे सूचीबद्ध कोड नमूने दें, यह मेरी समझ है कि संकलक क्या उत्पन्न करेगा।

केस 1: मूल्य द्वारा कब्जा / मूल्य द्वारा डिफ़ॉल्ट कब्जा

int x = 6;
auto lambda = [x]() { std::cout << x << std::endl; };

के बराबर होगा:

class __some_compiler_generated_name {
public:
    __some_compiler_generated_name(int x) : __x{x}{}
    void operator()() const { std::cout << __x << std::endl;}
private:
    int __x;
};

तो कई प्रतियाँ हैं, एक को कंस्ट्रक्टर पैरामीटर में कॉपी करने के लिए और एक सदस्य में कॉपी करने के लिए, जो वेक्टर आदि के प्रकारों के लिए महंगी होगी।

केस 2: संदर्भ द्वारा कैप्चर करें / संदर्भ द्वारा डिफ़ॉल्ट कैप्चर करें

int x = 6;
auto lambda = [&x]() { std::cout << x << std::endl; };

के बराबर होगा:

class __some_compiler_generated_name {
public:
    __some_compiler_generated_name(int& x) : x_{x}{}
    void operator()() const { std::cout << x << std::endl;}
private:
    int& x_;
};

पैरामीटर एक संदर्भ है और सदस्य एक संदर्भ है इसलिए कोई प्रतिलिपि नहीं है। वेक्टर आदि जैसे प्रकारों के लिए अच्छा है।

केस 3:

सामान्यीकृत init कैप्चर

auto lambda = [x = 33]() { std::cout << x << std::endl; };

मेरा खड़ा होना इस तरह से केस 1 के समान है कि इसे सदस्य में कॉपी किया जाता है।

मेरा अनुमान है कि संकलक समान कोड बनाता है ...

class __some_compiler_generated_name {
public:
    __some_compiler_generated_name() : __x{33}{}
    void operator()() const { std::cout << __x << std::endl;}
private:
    int __x;
};

इसके अलावा अगर मेरे पास निम्नलिखित हैं:

auto l = [p = std::move(unique_ptr_var)]() {
 // do something with unique_ptr_var
};

निर्माणकर्ता कैसा दिखेगा? क्या यह भी इसे सदस्य में स्थानांतरित करता है?


1
@ rafix07 उस मामले में उत्पन्न अंतर्दृष्टि कोड भी संकलित नहीं होगा (यह तर्क से अद्वितीय ptr सदस्य को कॉपी-आरंभ करने की कोशिश करता है)। सामान्य ज्ञान प्राप्त करने के लिए cppinsights उपयोगी है, लेकिन यह स्पष्ट रूप से यहाँ इस प्रश्न का उत्तर देने में सक्षम नहीं है।
मैक्स लैंगहॉफ़

आपको लगता है कि संकलन के पहले चरण के रूप में लैम्बडा टू फंक्शनलर्स का अनुवाद है, या आप केवल समकक्ष कोड (यानी एक ही व्यवहार) की तलाश कर रहे हैं? जिस तरह से एक विशिष्ट संकलक कोड उत्पन्न करता है (और यह कौन सा कोड उत्पन्न करता है) संकलक, संस्करण, वास्तुकला, झंडे आदि पर निर्भर करेगा, तो क्या आप एक विशिष्ट मंच के लिए पूछ रहे हैं? यदि नहीं, तो आपका प्रश्न वास्तव में उत्तर देने योग्य नहीं है। वास्तविक जेनरेट किए गए कोड के अलावा संभवतः आपके द्वारा सूचीबद्ध किए गए फ़ंक्शनलर्स की तुलना में अधिक कुशल होंगे (उदाहरण के लिए, इन-बिल्ड कंस्ट्रक्टर, अनावश्यक प्रतियों से बचना, आदि)।
सैंडर डी डाइकर

2
यदि आप रुचि रखते हैं कि C ++ मानक को इसके बारे में क्या कहना है, तो [expr.prim.lambda] देखें । उत्तर के रूप में संक्षेप में प्रस्तुत करना बहुत अधिक है।
सैंडर डी डाइकर

जवाबों:


2

इस सवाल का पूरी तरह से कोड में जवाब नहीं दिया जा सकता है। आप कुछ हद तक "समतुल्य" कोड लिखने में सक्षम हो सकते हैं, लेकिन मानक उस तरह से निर्दिष्ट नहीं है।

उस रास्ते से बाहर, चलो में गोता [expr.prim.lambda]। ध्यान देने वाली पहली बात यह है कि कंस्ट्रक्टर केवल में उल्लिखित हैं [expr.prim.lambda.closure]/13:

लैम्ब्डा-एक्सप्रेशन से जुड़े क्लोजर टाइप का कोई डिफॉल्ट कंस्ट्रक्टर नहीं होता है अगर लैम्ब्डा-एक्सप्रेशन में लैम्बडा-कैप्चर होता है और डिफॉल्ट डिफॉल्ट कंस्ट्रक्टर होता है। इसमें एक डिफॉल्ट कॉपी कंस्ट्रक्टर और एक डिफॉल्ट मूव कंस्ट्रक्टर ([class.copy.ctor]) है। यदि यह एक नष्ट कर दिया प्रतिलिपि असाइनमेंट ऑपरेटर है लैम्ब्डा अभिव्यक्ति एक है लैम्ब्डा पर कब्जा और डिफॉल्ट की कॉपी और चाल काम ऑपरेटरों अन्यथा ([class.copy.assign])। [ नोट: इन विशेष सदस्य कार्यों को सामान्य रूप से परिभाषित किया गया है, और इसलिए इन्हें हटाए जाने के रूप में परिभाषित किया जा सकता है। - अंतिम नोट ]

तो बल्ले से सही, यह स्पष्ट होना चाहिए कि निर्माणकर्ता औपचारिक रूप से नहीं हैं कि वस्तुओं को कैसे परिभाषित किया जाए। आप बहुत करीब हो सकते हैं (cppinsights.io उत्तर देखें), लेकिन विवरण अलग-अलग हैं (ध्यान दें कि केस 4 के लिए उस उत्तर में कोड कैसे संकलित नहीं करता है)।


केस 1 पर चर्चा करने के लिए ये मुख्य मानक खंड हैं:

[expr.prim.lambda.capture]/10

[...]
प्रतिलिपि द्वारा कब्जा की गई प्रत्येक इकाई के लिए, एक अनाम गैर-स्थैतिक डेटा सदस्य को क्लोजर प्रकार में घोषित किया जाता है। इन सदस्यों का घोषणा आदेश अनिर्दिष्ट है। इस तरह के डेटा सदस्य का प्रकार संदर्भित प्रकार है यदि इकाई किसी वस्तु का संदर्भ है, तो संदर्भित फ़ंक्शन प्रकार का एक अंतराल संदर्भ यदि इकाई किसी फ़ंक्शन का संदर्भ है, या संबंधित कैप्चर की गई इकाई का प्रकार अन्यथा। किसी अनाम यूनियन के सदस्य को कॉपी द्वारा कैप्चर नहीं किया जाएगा।

[expr.prim.lambda.capture]/11

लैम्ब्डा-एक्सप्रेशन के कंपाउंड-स्टेटमेंट के भीतर प्रत्येक आईडी-एक्सप्रेशन , जो कॉपी द्वारा कैप्चर की गई इकाई का ओड-यूज़ है, क्लोजर टाइप के संबंधित अनाम डेटा सदस्य तक पहुंच में तब्दील हो जाता है। [...]

[expr.prim.lambda.capture]/15

जब लंबोदर-अभिव्यक्ति का मूल्यांकन किया जाता है, तो प्रतिलिपि द्वारा कैप्चर की गई संस्थाओं का उपयोग परिणामस्वरूप क्लोजर ऑब्जेक्ट के प्रत्येक संबंधित गैर-स्थिर डेटा सदस्य को प्रत्यक्ष-प्रारंभ करने के लिए किया जाता है, और गैर-स्थैतिक डेटा सदस्य जो कि init-captures के अनुरूप होते हैं, प्रारंभिक रूप से होते हैं। संबंधित इनिशियलाइज़र (जो कॉपी-या प्रत्यक्ष-इनिशियलाइज़ेशन हो सकता है) द्वारा इंगित किया गया है। [...]

आइए इसे अपने मामले में लागू करें 1:

केस 1: मूल्य द्वारा कब्जा / मूल्य द्वारा डिफ़ॉल्ट कब्जा

int x = 6;
auto lambda = [x]() { std::cout << x << std::endl; };

इस लैम्ब्डा के बंद प्रकार में एक गैर-स्थैतिक डेटा सदस्य (चलो इसे कॉल करें __x) प्रकार का होगा int(क्योंकि xन तो एक संदर्भ है और न ही कोई फ़ंक्शन है), और xलैम्ब्डा बॉडी के भीतर एक्सेस करने के लिए इसे एक्सेस किया जाता है __x। जब हम लैम्ब्डा एक्सप्रेशन का मूल्यांकन करते हैं (यानी जब असाइन करते हैं lambda), तो हम डायरेक्ट-इनिशियलाइज़ __x करते हैं x

संक्षेप में, केवल एक प्रति लगती है । क्लोजर प्रकार का निर्माता शामिल नहीं है, और इसे "सामान्य" सी ++ में नोट करना संभव नहीं है (ध्यान दें कि क्लोजर प्रकार एक समग्र प्रकार भी नहीं है)।


संदर्भ पर कब्जा शामिल [expr.prim.lambda.capture]/12:

एक इकाई को संदर्भ द्वारा कैप्चर किया जाता है यदि यह अंतर्निहित या स्पष्ट रूप से कैप्चर किया गया हो लेकिन प्रतिलिपि द्वारा कैप्चर नहीं किया गया हो। यह अनिर्दिष्ट है कि क्या अतिरिक्त अनाम गैर-स्थैतिक डेटा सदस्यों को संदर्भ द्वारा कैप्चर की गई संस्थाओं के लिए क्लोजर प्रकार में घोषित किया गया है। [...]

संदर्भों के संदर्भ पर कब्जा करने के बारे में एक और पैराग्राफ है लेकिन हम कहीं भी ऐसा नहीं कर रहे हैं।

तो, केस 2 के लिए:

केस 2: संदर्भ द्वारा कैप्चर करें / संदर्भ द्वारा डिफ़ॉल्ट कैप्चर करें

int x = 6;
auto lambda = [&x]() { std::cout << x << std::endl; };

हमें पता नहीं है कि कोई सदस्य क्लोजर प्रकार में जोड़ा जाता है या नहीं। xभेड़ के बच्चे के शरीर में सीधे xबाहर का उल्लेख हो सकता है । यह संकलक पर निर्भर है, और यह मध्यवर्ती भाषा (जो संकलक से संकलक में भिन्न होती है) के कुछ रूप में करेगी, न कि C ++ कोड का स्रोत परिवर्तन।


Init कैप्चर विस्तृत हैं [expr.prim.lambda.capture]/6:

इन-कैप्चर व्यवहार करता है जैसे कि यह घोषित करता है और स्पष्ट रूप से उस रूप के एक चर को पकड़ता है auto init-capture ;जिसका घोषणात्मक क्षेत्र लंबोदर-अभिव्यक्ति का यौगिक-बयान है, सिवाय इसके:

  • (६.१) यदि कैप्चर कॉपी (नीचे देखें) से होता है, तो गैर-स्टेटिक डेटा मेंबर को कैप्चर और वैरिएबल के लिए घोषित किया जाता है, जिसे एक ही ऑब्जेक्ट को संदर्भित करने के दो अलग-अलग तरीकों के रूप में माना जाता है, जिसमें गैर-स्टैटिक डेटा का जीवनकाल होता है। सदस्य, और कोई अतिरिक्त प्रतिलिपि और विनाश नहीं किया जाता है, और
  • (6.2) यदि कैप्चर संदर्भ से होता है, तो क्लोजर ऑब्जेक्ट का जीवनकाल समाप्त होने पर चर का जीवनकाल समाप्त हो जाता है।

यह देखते हुए, आइए केस 3 देखें:

केस 3: सामान्यीकृत init कैप्चर

auto lambda = [x = 33]() { std::cout << x << std::endl; };

जैसा कि कहा गया है, इसे एक चर के रूप में कल्पना करें auto x = 33;और प्रतिलिपि द्वारा स्पष्ट रूप से कब्जा कर लिया जाए। यह चर लैम्ब्डा शरीर के भीतर केवल "दृश्यमान" है। जैसा कि [expr.prim.lambda.capture]/15पहले उल्लेख किया गया है, __xलैम्बडा अभिव्यक्ति के मूल्यांकन पर दिए गए इनिशलाइज़र द्वारा क्लोजर प्रकार ( पश्चात के लिए) के संबंधित सदस्य का आरंभीकरण है।

संदेह से बचने के लिए: इसका मतलब यह नहीं है कि चीजों को यहां दो बार शुरू किया गया है। auto x = 33;एक "के रूप में यदि" सरल कैप्चर के शब्दों के वारिस है, और वर्णित आरंभीकरण उन शब्दों के एक संशोधन है। केवल एक इनिशियलाइज़ेशन होता है।

यह मामला 4 को भी कवर करता है:

auto l = [p = std::move(unique_ptr_var)]() {
  // do something with unique_ptr_var
};

क्लोजर टाइप मेंबर को इनिशियलाइज़ किया जाता है __p = std::move(unique_ptr_var)जब लैम्ब्डा एक्सप्रेशन का मूल्यांकन किया जाता है (यानी जब lअसाइन किया गया हो)। करने के लिए पहुंच pलैम्ब्डा शरीर में करने के लिए पहुंच के रूप में तब्दील कर रहे हैं __p


टीएल; डीआर: केवल प्रतियां / इनिशियलाइज़ / मूव्स की न्यूनतम संख्या का प्रदर्शन किया जाता है (जैसा कि कोई उम्मीद / उम्मीद करेगा)। मैं मानूंगा कि लैम्बदास एक स्रोत परिवर्तन (अन्य संश्लिष्ट शर्करा के विपरीत) के संदर्भ में निर्दिष्ट नहीं हैं, क्योंकि निर्माणकर्ताओं के संदर्भ में चीजों को व्यक्त करने के लिए अत्यधिक संचालन की आवश्यकता होगी।

मुझे आशा है कि यह प्रश्न में व्यक्त आशंकाओं को सुलझाता है :)


9

केस 1 [x](){} : उत्पन्न निर्माता constअनावश्यक प्रतियों से बचने के लिए संभवतः- अयोग्य संदर्भ द्वारा अपना तर्क स्वीकार करेगा :

__some_compiler_generated_name(const int& x) : x_{x}{}

केस 2 [x&](){} : यहां आपकी धारणाएं सही हैं, xसंदर्भ द्वारा पारित और संग्रहीत है।


केस 3 [x = 33](){} : फिर से सही, xमूल्य द्वारा इनिशियलाइज़ किया जाता है।


केस 4 [p = std::move(unique_ptr_var)] : कंस्ट्रक्टर इस तरह दिखेगा:

    __some_compiler_generated_name(std::unique_ptr<SomeType>&& x) :
        x_{std::move(x)}{}

तो हाँ, unique_ptr_var"बंद" में चला गया है। प्रभावी आधुनिक C ++ में स्कॉट मेयर का आइटम 32 भी देखें ("ऑब्जेक्ट्स को बंद करने के लिए स्थानांतरित करने के लिए init कैप्चर का उपयोग करें")।


" const-संकल्पित" क्यों?
cpplearner

@cpplearner Mh, अच्छा सवाल। मुझे लगता है कि मैंने डाला है क्योंकि उन ^ ^ में से एक मानसिक आटोमैटिस को लात मारी गई है। कम से कम constयहां कुछ अस्पष्टता / बेहतर मैच के कारण चोट नहीं लग सकती है जब constभी गैर- आदि। वैसे भी, क्या आपको लगता है कि मुझे हटा देना चाहिए const?
lubgr

मुझे लगता है कि कॉन्स्ट को बने रहना चाहिए, क्या होगा, अगर तर्क वास्तव में पास है?
एकॉनगुआ

तो आप कह रहे हैं कि दो चाल (या कॉपी) निर्माण यहाँ होते हैं?
मैक्स लैंगहॉफ़

क्षमा करें, मेरा मतलब है केस 4 (चालों के लिए) और केस 1 (प्रतियों के लिए)। मेरे प्रश्न का प्रतिलिपि भाग आपके कथनों के आधार पर कोई अर्थ नहीं रखता (लेकिन मैं उन कथनों पर प्रश्न करता हूँ)।
मैक्स लैंगहॉफ़

5

Cppinsights.io का उपयोग करके, अटकलें लगाने की आवश्यकता कम है ।

केस 1:
कोड

#include <memory>

int main() {
    int x = 33;
    auto lambda = [x]() { std::cout << x << std::endl; };
}

संकलक उत्पन्न करता है

#include <iostream>

int main()
{
  int x = 6;

  class __lambda_5_16
  {
    int x;
    public: 
    inline void operator()() const
    {
      std::cout.operator<<(x).operator<<(std::endl);
    }

    // inline /*constexpr */ __lambda_5_16(const __lambda_5_16 &) = default;
    // inline /*constexpr */ __lambda_5_16(__lambda_5_16 &&) noexcept = default;
    public: __lambda_5_16(int _x)
    : x{_x}
    {}

  };

  __lambda_5_16 lambda = __lambda_5_16(__lambda_5_16{x});
}

केस 2:
कोड

#include <iostream>
#include <memory>

int main() {
    int x = 33;
    auto lambda = [&x]() { std::cout << x << std::endl; };
}

संकलक उत्पन्न करता है

#include <iostream>

int main()
{
  int x = 6;

  class __lambda_5_16
  {
    int & x;
    public: 
    inline void operator()() const
    {
      std::cout.operator<<(x).operator<<(std::endl);
    }

    // inline /*constexpr */ __lambda_5_16(const __lambda_5_16 &) = default;
    // inline /*constexpr */ __lambda_5_16(__lambda_5_16 &&) noexcept = default;
    public: __lambda_5_16(int & _x)
    : x{_x}
    {}

  };

  __lambda_5_16 lambda = __lambda_5_16(__lambda_5_16{x});
}

केस 3:
कोड

#include <iostream>

int main() {
    auto lambda = [x = 33]() { std::cout << x << std::endl; };
}

संकलक उत्पन्न करता है

#include <iostream>

int main()
{

  class __lambda_4_16
  {
    int x;
    public: 
    inline void operator()() const
    {
      std::cout.operator<<(x).operator<<(std::endl);
    }

    // inline /*constexpr */ __lambda_4_16(const __lambda_4_16 &) = default;
    // inline /*constexpr */ __lambda_4_16(__lambda_4_16 &&) noexcept = default;
    public: __lambda_4_16(int _x)
    : x{_x}
    {}

  };

  __lambda_4_16 lambda = __lambda_4_16(__lambda_4_16{33});
}

केस 4 (अनाधिकारिक):
कोड

#include <iostream>
#include <memory>

int main() {
    auto x = std::make_unique<int>(33);
    auto lambda = [x = std::move(x)]() { std::cout << *x << std::endl; };
}

संकलक उत्पन्न करता है

// EDITED output to minimize horizontal scrolling
#include <iostream>
#include <memory>

int main()
{
  std::unique_ptr<int, std::default_delete<int> > x = 
      std::unique_ptr<int, std::default_delete<int> >(std::make_unique<int>(33));

  class __lambda_6_16
  {
    std::unique_ptr<int, std::default_delete<int> > x;
    public: 
    inline void operator()() const
    {
      std::cout.operator<<(x.operator*()).operator<<(std::endl);
    }

    // inline __lambda_6_16(const __lambda_6_16 &) = delete;
    // inline __lambda_6_16(__lambda_6_16 &&) noexcept = default;
    public: __lambda_6_16(std::unique_ptr<int, std::default_delete<int> > _x)
    : x{_x}
    {}

  };

  __lambda_6_16 lambda = __lambda_6_16(__lambda_6_16{std::unique_ptr<int, 
                                                     std::default_delete<int> >
                                                         (std::move(x))});
}

और मेरा मानना ​​है कि यह आखिरी टुकड़ा आपके प्रश्न का उत्तर देता है। कंस्ट्रक्टर में एक चाल होती है, लेकिन [तकनीकी रूप से] नहीं।

खुद को constपकड़ नहीं रहे हैं , लेकिन आप देख सकते हैं कि operator()फ़ंक्शन है। स्वाभाविक रूप से, यदि आपको कैप्चर को संशोधित करने की आवश्यकता है, तो आप लैम्बडा को चिह्नित करते हैं mutable


अंतिम मामले के लिए आप जो कोड दिखाते हैं, वह भी संकलित नहीं करता है। निष्कर्ष "एक चाल होती है, लेकिन निर्माण में [तकनीकी रूप से] नहीं" उस कोड द्वारा समर्थित नहीं किया जा सकता है।
मैक्स लैंगहॉफ़

संहिता मामले में 4 की सबसे निश्चित रूप से अपने Mac पर संकलित करता है। मुझे आश्चर्य है कि cppinsights से उत्पन्न विस्तारित कोड संकलन नहीं करता है। इस बिंदु पर, साइट मेरे लिए काफी विश्वसनीय रही है। मैं उनके साथ एक मुद्दा उठाऊंगा। संपादित करें: मैंने पुष्टि की है कि उत्पन्न कोड संकलित नहीं करता है; यह इस संपादन के बिना स्पष्ट नहीं था।
sweenish

1
ब्याज के मामले में मुद्दे के लिए लिंक: github.com/andreasfertig/cppinsights/issues/258 मैं अभी भी SFINAE जैसी चीजों के परीक्षण के लिए साइट की सिफारिश करता हूं और चाहे निहितार्थ हो या न हों।
स्वैनिश
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.