नए C ++ मानकों के बारे में

आज मेरे पास एक बहुत छोटी पोस्ट है। मैं शायद इसे नहीं लिखूंगा, लेकिन Habré की टिप्पणियों में आप अक्सर यह राय पा सकते हैं कि पेशेवरों की स्थिति खराब हो रही है, समिति यह स्पष्ट नहीं करती है कि अस्पष्ट क्यों है, और आम तौर पर मुझे मेरा 2007 वापस दे देना चाहिए। और फिर ऐसा स्पष्ट उदाहरण अचानक सामने आया।


लगभग पांच साल पहले, मैंने C ++ में करी बनाने के तरीके के बारे में लिखा था । ठीक है, अगर आप foo(bar, baz, quux) लिख सकते हैं, तो आप Curry(foo)(bar)(baz)(quux) । तब C ++ 14 बस बाहर आया था और संकलक द्वारा मुश्किल से समर्थन किया गया था, इसलिए कोड ने केवल C ++ 11 चिप्स का उपयोग किया (साथ ही C ++ 14 से लाइब्रेरी कार्यों को अनुकरण करने के लिए बैसाखी के एक जोड़े)।


और फिर मैं फिर से इस कोड पर ठोकर खाई, और मेरी आँखें चोट लगी हैं कि यह कैसे क्रिया है। साथ ही, मैंने कैलेंडर को बहुत पहले नहीं बदल दिया और देखा कि अब वर्ष 2019 है, और आप देख सकते हैं कि C ++ 17 कैसे आपके जीवन को आसान बना सकता है।


क्या हम देखेंगे?


ठीक है, देखते हैं।


मूल कार्यान्वयन, जिसमें से हम नृत्य करेंगे, कुछ इस तरह दिखता है:


 template<typename F, typename... PrevArgs> class CurryImpl { const F m_f; const std::tuple<PrevArgs...> m_prevArgs; public: CurryImpl (F f, const std::tuple<PrevArgs...>& prev) : m_f { f } , m_prevArgs { prev } { } private: template<typename T> std::result_of_t<F (PrevArgs..., T)> invoke (const T& arg, int) const { return invokeIndexed (arg, std::index_sequence_for<PrevArgs...> {}); } template<typename IF> struct Invoke { template<typename... IArgs> auto operator() (IF fr, IArgs... args) -> decltype (fr (args...)) { return fr (args...); } }; template<typename R, typename C, typename... Args> struct Invoke<R (C::*) (Args...)> { R operator() (R (C::*ptr) (Args...), C c, Args... rest) { return (c.*ptr) (rest...); } R operator() (R (C::*ptr) (Args...), C *c, Args... rest) { return (c->*ptr) (rest...); } }; template<typename T, std::size_t... Is> auto invokeIndexed (const T& arg, std::index_sequence<Is...>) const -> decltype (Invoke<F> {} (m_f, std::get<Is> (m_prevArgs)..., arg)) { return Invoke<F> {} (m_f, std::get<Is> (m_prevArgs)..., arg); } template<typename T> auto invoke (const T& arg, ...) const -> CurryImpl<F, PrevArgs..., T> { return { m_f, std::tuple_cat (m_prevArgs, std::tuple<T> { arg }) }; } public: template<typename T> auto operator() (const T& arg) const -> decltype (invoke (arg, 0)) { return invoke (arg, 0); } }; template<typename F> CurryImpl<F> Curry (F f) { return { f, {} }; } 

m_f में संग्रहित m_f है, m_prevArgs - पिछली कॉल पर संग्रहीत तर्क।


यहां operator() को यह निर्धारित करना चाहिए कि क्या पहले से बचाए गए फंटर को कॉल करना संभव है, या क्या तर्क जमा करना जारी रखना आवश्यक है, इसलिए यह invoke सहायक का उपयोग करके एक काफी मानक SFINAE बनाता है। इसके अलावा, फ़ंक्टर को कॉल करने के लिए (या इसकी कॉलिबिलिटी की जांच करने के लिए), हम यह समझने के लिए यह करने के लिए एक और SFINAE परत के साथ यह सब कवर करते हैं (क्योंकि हमें पॉइंटर को सदस्य को कॉल करने की आवश्यकता है और, कहते हैं, फ्री फ़ंक्शन अलग-अलग तरीकों से) और इसके लिए हम Invoke हेल्पर संरचना का उपयोग करते हैं, जो कि शायद अधूरा है ... संक्षेप में, बहुत सारी चीजें।


खैर, यह बात पूरी तरह घृणित रूप से चलती है, हमारे समय के प्लस चिन्ह के दिल में मीठे शब्दार्थ, परिपूर्ण अग्रेषण और अन्य शब्दों के साथ काम करती है। इसे सुधारना आवश्यक से थोड़ा अधिक कठिन होगा, क्योंकि सीधे हल किए गए कार्य के अलावा, कोड का एक गुच्छा भी है जो इससे संबंधित नहीं है।


खैर, फिर से, C ++ 11 में std::index_sequence और संबंधित चीजें, या अन्य उपनाम std::result_of_t जैसी std::result_of_t , इसलिए शुद्ध C ++ 11 कोड और भी कठिन होगा।


तो, आखिरकार, हम C ++ 17 पर चलते हैं।


सबसे पहले, हमें रिटर्न प्रकार operator() निर्दिष्ट करने की आवश्यकता नहीं है, हम बस लिख सकते हैं:


 template<typename T> auto operator() (const T& arg) const { return invoke (arg, 0); } 

तकनीकी रूप से, यह बिल्कुल समान नहीं है ("लिंकिंग" अलग-अलग तरीकों से प्रदर्शित होता है), लेकिन हमारे कार्य के ढांचे के भीतर यह आवश्यक नहीं है।


इसके अलावा, हमें संग्रहित तर्कों के साथ m_f जांच करने के लिए अपने हाथों से SFINAE करने की आवश्यकता नहीं है। C ++ 17 हमें दो शांत विशेषताएं प्रदान करता है: constexpr if और std::is_invocable । नए operator() के कंकाल को लिखने से पहले हमारे पास मौजूद हर चीज को फेंक दें:


 template<typename T> auto operator() (const T& arg) const { if constexpr (std::is_invocable_v<F, PrevArgs..., T>) //   else //       arg } 

दूसरी शाखा तुच्छ है, आप उस कोड को कॉपी कर सकते हैं जो पहले से था:


 template<typename T> auto operator() (const T& arg) const { if constexpr (std::is_invocable_v<F, PrevArgs..., T>) //   else return CurryImpl<F, PrevArgs..., T> { m_f, std::tuple_cat (m_prevArgs, std::tuple<T> { arg }) }; } 

पहली शाखा अधिक दिलचस्प होगी। हमें m_f को कॉल करने की आवश्यकता है, m_prevArgs में संग्रहीत सभी तर्कों को पास करते m_prevArgs , प्लस arg । सौभाग्य से, हमें अब किसी भी integer_sequence आवश्यकता नहीं है: C ++ 17 में एक मानक पुस्तकालय फ़ंक्शन std::apply जो फ़ंक्शन को tuple में संग्रहीत तर्कों के साथ कॉल std::apply लिए std::apply है। केवल हमें डमी के अंत में एक और तर्क ( arg ) डालने की आवश्यकता है, इसलिए हम या तो std::tuple_cat , या सिर्फ unpack std::apply कर सकते हैं 'हम मौजूदा डमी जेनेरिक लैम्ब्डा (C ++ के बाद दिखाई देने वाली एक और विशेषता) का उपयोग कर सकते हैं 11, हालांकि 17 वें में नहीं!)। मेरे अनुभव में, तत्काल डमी को धीमा करना (निश्चित समय में, निश्चित रूप से) है, इसलिए मैं दूसरा विकल्प चुनूंगा। लैम्ब्डा में ही, मुझे m_f को कॉल करने की आवश्यकता है, और इसे सही ढंग से करने के लिए, मैं लाइब्रेरी फ़ंक्शन का उपयोग कर सकता हूं जो C ++ 17, std::invoke m_f में दिखाई देता है, हाथ से लिखे गए Invoke हेल्पर को बाहर फेंककर:


 template<typename T> auto operator() (const T& arg) const { if constexpr (std::is_invocable_v<F, PrevArgs..., T>) { auto wrapper = [this, &arg] (auto&&... args) { return std::invoke (m_f, std::forward<decltype (args)> (args)..., arg); }; return std::apply (std::move (wrapper), m_prevArgs); } else return CurryImpl<F, PrevArgs..., T> { m_f, std::tuple_cat (m_prevArgs, std::tuple<T> { arg }) }; } 

यह नोट करना उपयोगी है कि कैसे auto कटौती रिटर्न प्रकार आपको विभिन्न शाखाओं के विभिन्न प्रकारों के मूल्यों को वापस करने की अनुमति देता है if constexpr


किसी भी मामले में, यह मूल रूप से सभी है। या आवश्यक दोहन के साथ:


 template<typename F, typename... PrevArgs> class CurryImpl { const F m_f; const std::tuple<PrevArgs...> m_prevArgs; public: CurryImpl (F f, const std::tuple<PrevArgs...>& prev) : m_f { f } , m_prevArgs { prev } { } template<typename T> auto operator() (const T& arg) const { if constexpr (std::is_invocable_v<F, PrevArgs..., T>) { auto wrapper = [this, &arg] (auto&&... args) { return std::invoke (m_f, std::forward<decltype (args)> (args)..., arg); }; return std::apply (std::move (wrapper), m_prevArgs); } else return CurryImpl<F, PrevArgs..., T> { m_f, std::tuple_cat (m_prevArgs, std::tuple<T> { arg }) }; } }; template<typename F, typename... Args> CurryImpl<F, Args...> Curry (F f, Args&&... args) { return { f, std::forward_as_tuple (std::forward<Args> (args)...) }; } 

मुझे लगता है कि यह मूल संस्करण पर एक महत्वपूर्ण सुधार है। और इसे पढ़ना आसान है। किसी तरह उबाऊ भी, कोई चुनौती नहीं


इसके अलावा, हम Curry फंक्शन से भी छुटकारा पा सकते हैं और सीधे कटौती गाइड्स पर भरोसा करते CurryImpl सीधे CurryImpl उपयोग कर CurryImpl हैं, लेकिन यह सबसे अच्छा तब होता है जब हम परफेक्ट फॉरवर्डिंग और इस तरह से व्यवहार करते हैं। जो आसानी से हमें लाता है ...


अब यह बिल्कुल स्पष्ट है कि यह दलीलों की नकल करने के मामले में कितना भयानक है, यह दुर्भाग्यपूर्ण पूर्ण अग्रेषण और पसंद है। लेकिन इससे भी महत्वपूर्ण बात यह है कि इसे ठीक करना अब बहुत आसान है। लेकिन, हम, अगले पोस्ट में किसी भी तरह यह करेंगे।


एक निष्कर्ष के बजाय


सबसे पहले, C ++ 20 में, std::bind_front दिखाई देगा, जो मेरे उपयोगकर्ता मामलों के शेर के हिस्से को कवर करेगा जिसमें मुझे ऐसी चीज़ की आवश्यकता है। आप आम तौर पर इसे दूर फेंक सकते हैं। दु: खी।


दूसरे, पेशेवरों पर लिखना आसान हो रहा है, भले ही आप मेटाप्रोग्रामिंग के साथ किसी तरह का टेम्पलेट कोड लिखें। अब आपको यह सोचने की ज़रूरत नहीं है कि एसएफआईएनएई विकल्प चुनने के लिए, डमी कैसे अनपैक करें, फ़ंक्शन कैसे कॉल करें। बस ले लो और लिखो if constexpr , std::apply , std::invoke if constexpr । एक तरफ, यह अच्छा है; मैं C ++ 14 या विशेष रूप से, 11 पर वापस नहीं जाना चाहता। दूसरी ओर, ऐसा महसूस होता है कि शेरों के कौशल की परत अनावश्यक होती जा रही है। नहीं, यह अभी भी टेम्प्लेट पर ऐसा कुछ करने में सक्षम होने के लिए उपयोगी है और यह समझें कि यह लाइब्रेरी मैजिक आपके अंदर कैसे काम करता है, लेकिन अगर आपको हर समय इसकी आवश्यकता होती है, तो यह अब बहुत कम है। यह कुछ अजीब भावनाओं का कारण बनता है।

Source: https://habr.com/ru/post/hi466985/


All Articles