क्या जीसीसी 9 एसटीडी की वैधता की स्थिति से बचा रहा है :: संस्करण की अनुमति है?


14

मैंने हाल ही में एक Reddit चर्चा का पालन किया है जो कंपाइलरों में std::visitअनुकूलन की एक अच्छी तुलना की ओर ले जाता है । मैंने निम्नलिखित पर ध्यान दिया: https://godbolt.org/z/D2Q5ED

GCC9 और Clang9 दोनों (मुझे लगता है कि वे समान stdlib साझा करते हैं) सभी प्रकार की कुछ शर्तों को पूरा करने पर एक वैधता अपवाद की जाँच और फेंकने के लिए कोड उत्पन्न नहीं करते हैं। यह बेहतर कोडजेन की ओर जाता है, इसलिए मैंने MSVC STL के साथ एक मुद्दा उठाया और इस कोड के साथ प्रस्तुत किया गया:

template <class T>
struct valueless_hack {
  struct tag {};
  operator T() const { throw tag{}; }
};

template<class First, class... Rest>
void make_valueless(std::variant<First, Rest...>& v) {
  try { v.emplace<0>(valueless_hack<First>()); }
  catch(typename valueless_hack<First>::tag const&) {}
}

दावा किया गया था कि यह किसी भी संस्करण बेकार बना देता है, और पढ़ने के दस्तावेज यह होना चाहिए:

सबसे पहले, वर्तमान में निहित मूल्य (यदि कोई हो) को नष्ट कर देता है। फिर निहित मूल्य को प्रत्यक्ष-आरंभ करता है जैसे T_Iकि तर्कों के साथ प्रकार के मूल्य का निर्माण std::forward<Args>(args)....यदि एक अपवाद फेंक दिया जाता है, तो *thisvalueless_by_exception बन सकता है।

मुझे समझ में नहीं आ रहा है: इसे "मई" के रूप में क्यों कहा गया है? यदि पूरे ऑपरेशन को फेंकता है तो क्या पुरानी अवस्था में रहना कानूनी है? क्योंकि ऐसा जीसीसी करता है:

  // For suitably-small, trivially copyable types we can create temporaries
  // on the stack and then memcpy them into place.
  template<typename _Tp>
    struct _Never_valueless_alt
    : __and_<bool_constant<sizeof(_Tp) <= 256>, is_trivially_copyable<_Tp>>
    { };

और बाद में यह (सशर्त रूप से) कुछ ऐसा करता है:

T tmp  = forward(args...);
reset();
construct(tmp);
// Or
variant tmp(inplace_index<I>, forward(args...));
*this = move(tmp);

इसलिए मूल रूप से यह एक अस्थायी बनाता है, और यदि वह प्रतियां सफल हो जाती है / उसे वास्तविक स्थान पर ले जाती है।

IMO यह "पहले का उल्लंघन है, जो कि वर्तमान में निहित मूल्य को नष्ट करता है" जैसा कि इसने कहा है। जैसा कि मैंने मानक पढ़ा है, तब v.emplace(...)वेरिएंट में करंट वैल्यू हमेशा नष्ट हो जाने के बाद और नया टाइप या तो सेट टाइप या वैल्युलेस होता है।

मुझे लगता है कि हालत is_trivially_copyableउन सभी प्रकारों को शामिल करती है जिनमें एक अवलोकन योग्य विध्वंसक है। तो यह भी इस प्रकार हो सकता है: "जैसा कि अगर वैरिएंट को पुराने मूल्य के साथ पुनर्निवेशित किया जाता है" या तो। लेकिन वेरिएंट की स्थिति एक अवलोकन प्रभाव है। तो क्या मानक वास्तव में अनुमति देता है, जो emplaceवर्तमान मूल्य को नहीं बदलता है?

मानक उद्धरण के जवाब में संपादित करें:

फिर निहित मूल्य को इनिशियलाइज़ करता है जैसे कि डायरेक्ट-नॉन-लिस्ट-इनिशियलाइज़ेशन टाइप ऑफ़ द टाईल्स विथ दलील std​::​forward<Args>(args)...

क्या T tmp {std​::​forward<Args>(args)...}; this->value = std::move(tmp);वास्तव में उपरोक्त के एक वैध कार्यान्वयन के रूप में गिना जाता है? क्या इसका अर्थ "जैसे कि" है?

जवाबों:


7

मुझे लगता है कि मानक का महत्वपूर्ण हिस्सा यह है:

से https://timsong-cpp.github.io/cppwp/n4659/variant.mod#12

23.7.3.4 मोदी fi ers

(...)

टेम्पलेट variant_alternative_t> & emplace (Args && ... args);

(...) यदि निहित मूल्य के आरंभीकरण के दौरान एक अपवाद को फेंक दिया जाता है, तो वेरिएंट एक मान नहीं रख सकता है

यह कहता है "हो सकता है" नहीं "चाहिए"। मैं उम्मीद करूंगा कि यह जानबूझकर किया जाएगा ताकि जीसीसी द्वारा उपयोग किए जाने वाले कार्यान्वयन की अनुमति मिल सके।

जैसा कि आपने खुद का उल्लेख किया है, यह केवल तभी संभव है जब सभी विकल्पों के विध्वंसक तुच्छ हैं और इस प्रकार अप्रमाणिक हैं क्योंकि पिछले मूल्य को नष्ट करना आवश्यक है।

अनुवर्ती सवाल:

Then initializes the contained value as if direct-non-list-initializing a value of type TI with the arguments std​::​forward<Args>(args)....

क्या T tmp {std :: फॉरवर्ड (args) ...}; यह-> मूल्य = एसटीडी :: चाल (टीएमपी); वास्तव में उपरोक्त के एक वैध कार्यान्वयन के रूप में गिना जाता है? क्या इसका अर्थ "जैसे कि" है?

हां, क्योंकि उन प्रकारों के लिए जो तुच्छ रूप से प्रतिलिपि योग्य हैं , इसलिए अंतर का पता लगाने का कोई तरीका नहीं है, इसलिए कार्यान्वयन ऐसा व्यवहार करता है जैसे कि मूल्य को वर्णित किया गया था। यह काम नहीं करेगा यदि प्रकार तुच्छ रूप से प्रतिलिपि योग्य नहीं था।


दिलचस्प। मैंने अनुवर्ती / स्पष्टीकरण अनुरोध के साथ प्रश्न को अद्यतन किया। जड़ है: क्या प्रतिलिपि / चाल की अनुमति है? मैं शब्दांकन से बहुत भ्रमित हूं might/mayक्योंकि मानक यह नहीं बताता है कि विकल्प क्या है।
फ्लेमफायर 11

मानक बोली के लिए इसे स्वीकार करना और there is no way to detect the difference
फ्लेमफायर

5

तो क्या मानक वास्तव में अनुमति देता है, जो emplaceवर्तमान मूल्य को नहीं बदलता है?

हाँ। emplaceकोई लीक नहीं होने की मूल गारंटी प्रदान करेगा (यानी, निर्माण और विनाशकारी दुष्प्रभावों का उत्पादन करते समय वस्तु जीवनकाल का सम्मान करना), लेकिन जब संभव हो, तो उसे मजबूत गारंटी प्रदान करने की अनुमति दी जाती है (यानी, एक ऑपरेशन विफल होने पर मूल स्थिति रखी जाती है)।

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

हालांकि, छोटे तुच्छ रूप से प्रतिलिपि योग्य प्रकारों के लिए, बहुत अधिक ओवरहेड (यहां तक ​​कि इस मामले में एक चेक से बचने के लिए एक प्रदर्शन को बढ़ावा देने के बिना) मजबूत गारंटी प्रदान करना संभव है। इसलिए, कार्यान्वयन यह करता है। यह मानक-अनुरूप है: कार्यान्वयन अभी भी मानक द्वारा आवश्यक बुनियादी गारंटी प्रदान करता है, बस अधिक उपयोगकर्ता-अनुकूल तरीके से।

मानक उद्धरण के जवाब में संपादित करें:

फिर निहित मूल्य को इनिशियलाइज़ करता है जैसे कि डायरेक्ट-नॉन-लिस्ट-इनिशियलाइज़ेशन टाइप ऑफ़ द टाईल्स विथ दलील std​::​forward<Args>(args)...

क्या T tmp {std​::​forward<Args>(args)...}; this->value = std::move(tmp);वास्तव में उपरोक्त के एक वैध कार्यान्वयन के रूप में गिना जाता है? क्या इसका अर्थ "जैसे कि" है?

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


मैं तर्क युक्त तर्क से पूरी तरह सहमत हूं। मैं सिर्फ अनिश्चित हूँ यह वास्तव में मानक है? क्या आप इसे किसी भी चीज़ के साथ वापस कर सकते हैं?
फ्लेमफायर

@Flamefire Hmm ... सामान्य तौर पर, मानक कार्यक्षमताएं मूल गारंटी प्रदान करती हैं (जब तक कि उपयोगकर्ता जो कुछ भी प्रदान करता है उसके साथ कुछ गलत है), और std::variantउसे तोड़ने का कोई कारण नहीं है। मैं मानता हूं कि इसे मानक के शब्दांकन में और अधिक स्पष्ट किया जा सकता है, लेकिन यह मूल रूप से मानक पुस्तकालय के काम का हिस्सा है। और FYI, P0088 प्रारंभिक प्रस्ताव था।
LF

धन्यवाद। अंदर एक और अधिक स्पष्ट विनिर्देश है: if an exception is thrown during the call toT’s constructor, valid()will be false;ताकि इस "अनुकूलन" पर रोक लगाई
फ्लेमफायर

हाँ। emplaceP0088 के तहत की विशिष्टताException safety
ज्वालामुखी

@Flamefire मूल प्रस्ताव और संस्करण में मतदान के बीच एक विसंगति लगता है। अंतिम संस्करण "हो सकता है" शब्दों में बदल गया।
LF
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.