टी एल; डॉ
इससे पहले कि आप इस पूरी पोस्ट को पढ़ने का प्रयास करें, यह जानिए:
- प्रस्तुत मुद्दे का हल खुद ही मिल गया है , लेकिन मैं अभी भी यह जानने के लिए उत्सुक हूं कि क्या विश्लेषण सही है;
- मैंने समाधान को एक
fameta::counter
वर्ग में पैक किया है जो कुछ शेष क्वैरकों को हल करता है। आप इसे जीथब पर पा सकते हैं ; - आप इसे काम पर देख सकते हैं ।
यह सब कब प्रारंभ हुआ
चूंकि फिलिप रोसेन ने खोज / आविष्कार किया था, 2015 में, काला जादू जो कि समय काउंटरों को संकलित करता है C ++ में हैं , मुझे डिवाइस के साथ हल्के ढंग से जुनून है, इसलिए जब सीडब्ल्यूजी ने फैसला किया कि कार्यक्षमता को मुझे निराश करना पड़ा , लेकिन फिर भी उम्मीद है कि उनका मन उन्हें कुछ सम्मोहक उपयोग के मामलों को दिखाकर बदला जा सकता है।
फिर, कुछ साल पहले मैंने एक बार फिर से इस पर एक नज़र रखने का फैसला किया, ताकि uberswitch es को नेस्ट किया जा सके - एक दिलचस्प उपयोग मामला, मेरी राय में - केवल यह पता लगाने के लिए कि यह अब नए संस्करणों के साथ काम नहीं करेगा उपलब्ध संकलक, भले ही 2118 मुद्दा (और अभी भी है ) खुले राज्य में था: कोड संकलित करेगा, लेकिन काउंटर नहीं बढ़ेगा।
समस्या रोसेन की वेबसाइट पर और हाल ही में स्टैकओवरफ्लो पर भी बताई गई है : क्या C ++ संकलन-समय काउंटर का समर्थन करता है?
कुछ दिनों पहले मैंने फिर से मुद्दों को सुलझाने की कोशिश की
मैं समझना चाहता था कि संकलक में क्या बदलाव आया है, जो प्रतीत होता है कि अभी भी वैध सी ++ है, अब और काम नहीं करता। उस अंत तक, मैंने किसी के बारे में बात करने के लिए विस्तृत और दूर का अंतर खोजा है, लेकिन कोई फायदा नहीं हुआ। इसलिए मैंने प्रयोग करना शुरू कर दिया है और कुछ निष्कर्षों पर आया हूं, कि मैं यहां और अधिक जानकार-से-खुद से प्रतिक्रिया प्राप्त करने की उम्मीद कर रहा हूं।
नीचे मैं स्पष्टता के लिए रोसेन का मूल कोड प्रस्तुत कर रहा हूं। यह कैसे काम करता है, इसकी व्याख्या के लिए कृपया उनकी वेबसाइट देखें :
template<int N>
struct flag {
friend constexpr int adl_flag (flag<N>);
};
template<int N>
struct writer {
friend constexpr int adl_flag (flag<N>) {
return N;
}
static constexpr int value = N;
};
template<int N, int = adl_flag (flag<N> {})>
int constexpr reader (int, flag<N>) {
return N;
}
template<int N>
int constexpr reader (float, flag<N>, int R = reader (0, flag<N-1> {})) {
return R;
}
int constexpr reader (float, flag<0>) {
return 0;
}
template<int N = 1>
int constexpr next (int R = writer<reader (0, flag<32> {}) + N>::value) {
return R;
}
int main () {
constexpr int a = next ();
constexpr int b = next ();
constexpr int c = next ();
static_assert (a == 1 && b == a+1 && c == b+1, "try again");
}
जी ++ और क्लैंग ++ हाल-ईश कंपाइलर दोनों के साथ, next()
हमेशा रिटर्न होता है। थोड़ा प्रयोग करने के बाद, जी ++ के साथ कम से कम समस्या यह प्रतीत होती है कि एक बार कंपाइलर फंक्शंस डिफॉल्ट मापदंडों का मूल्यांकन करने के बाद पहली बार फ़ंक्शन को कॉल करता है, किसी भी बाद में कॉल। उन कार्यों ने डिफ़ॉल्ट मापदंडों के पुनर्मूल्यांकन को ट्रिगर नहीं किया है, इस प्रकार कभी भी नए कार्यों को तात्कालिक नहीं किया जाता है लेकिन हमेशा पहले के तात्कालिक लोगों का जिक्र किया जाता है।
पहले सवाल
- क्या आप वास्तव में मेरे इस निदान से सहमत हैं?
- यदि हाँ, तो क्या यह नया व्यवहार मानक द्वारा अनिवार्य है? क्या पिछला वाला बग था?
- यदि नहीं, तो समस्या क्या है?
उपरोक्त को ध्यान में रखते हुए, मैं चारों ओर एक काम के साथ आया: प्रत्येक को चिह्नित करें next()
इनवोकेशन को एक नीरस रूप से बढ़ती हुई अद्वितीय आईडी के साथ , जिससे केल को पास किया जाए, ताकि कोई कॉल समान न हो, इसलिए कंपाइलर को सभी तर्कों का पुनर्मूल्यांकन करने के लिए मजबूर करना हर बार।
ऐसा नहीं है कि ऐसा करने के लिए एक बोझ लगता है, लेकिन इसके बारे में सोच एक बस मानक इस्तेमाल कर सकते हैं __LINE__
या __COUNTER__
की तरह (जहां उपलब्ध हो) मैक्रो, एक में छिपा counter_next()
समारोह की तरह मैक्रो।
तो मैं निम्नलिखित के साथ आया, कि मैं सबसे सरल रूप में प्रस्तुत करता हूं जो उस समस्या को दिखाता है जिसके बारे में मैं बाद में बात करूंगा।
template <int N>
struct slot;
template <int N>
struct slot {
friend constexpr auto counter(slot<N>);
};
template <>
struct slot<0> {
friend constexpr auto counter(slot<0>) {
return 0;
}
};
template <int N, int I>
struct writer {
friend constexpr auto counter(slot<N>) {
return I;
}
static constexpr int value = I-1;
};
template <int N, typename = decltype(counter(slot<N>()))>
constexpr int reader(int, slot<N>, int R = counter(slot<N>())) {
return R;
};
template <int N>
constexpr int reader(float, slot<N>, int R = reader(0, slot<N-1>())) {
return R;
};
template <int N>
constexpr int next(int R = writer<N, reader(0, slot<N>())+1>::value) {
return R;
}
int a = next<11>();
int b = next<34>();
int c = next<57>();
int d = next<80>();
आप गॉडबोल्ट के ऊपर के परिणामों का निरीक्षण कर सकते हैं , जिसे मैंने लज़ीज़ के लिए स्क्रीनशॉट दिया है।
और जैसा कि आप देख सकते हैं, ट्रंक जी ++ और क्लैंग ++ के साथ 7.0.0 तक यह काम करता है! काउंटर अपेक्षित रूप से 0 से 3 तक बढ़ जाता है, लेकिन 7.0.0 से ऊपर क्लैंग ++ संस्करण के साथ ऐसा नहीं होता है ।
चोट के लिए अपमान को जोड़ने के लिए, मैं वास्तव में संस्करण 7.0.0 क्रैश करने के लिए clang ++ बनाने में कामयाब रहा हूं, बस मिश्रण में एक "संदर्भ" पैरामीटर जोड़कर, जैसे कि काउंटर वास्तव में उस संदर्भ से बंधा हुआ है और, जैसे कि, कर सकते हैं किसी भी समय एक नया संदर्भ परिभाषित किया जाता है, जो संभावित रूप से अनंत मात्रा में काउंटरों का उपयोग करने की संभावना के लिए खुलता है। इस संस्करण के साथ, 7.0.0 वर्जन के ऊपर क्लैंग ++ दुर्घटना नहीं करता है, लेकिन फिर भी अपेक्षित परिणाम नहीं देता है। देवभूमि पर रहते हैं ।
क्या चल रहा था, इसके बारे में किसी भी सुराग के नुकसान पर, मैंने cppinsights.io वेबसाइट की खोज की है , जो कि टेम्पलेट्स को कैसे और कब त्वरित रूप से देखता है। उस सेवा का उपयोग करना जो मुझे लगता है कि हो रहा है, यह है कि क्लैंग ++ वास्तव में किसी भी फ़ंक्शन को परिभाषित नहीं करताfriend constexpr auto counter(slot<N>)
है जब भी writer<N, I>
त्वरित किया जाता है।
counter(slot<N>)
किसी भी एन के लिए स्पष्ट रूप से कॉल करने की कोशिश करना जो पहले से ही तत्काल होना चाहिए था इस परिकल्पना को आधार देता है।
हालांकि, अगर मैं writer<N, I>
किसी दिए गए के लिए स्पष्ट रूप से तुरंत प्रयास करता हूं N
और I
जिसे पहले से ही तत्काल किया जाना चाहिए था, तो क्लैंग ++ शिकायत को फिर से परिभाषित करता है friend constexpr auto counter(slot<N>)
।
उपरोक्त परीक्षण करने के लिए, मैंने पिछले स्रोत कोड में दो और लाइनें जोड़ी हैं।
int test1 = counter(slot<11>());
int test2 = writer<11,0>::value;
आप यह सब अपने लिए देवभूमि पर देख सकते हैं । नीचे स्क्रीनशॉट।
तो, ऐसा प्रतीत होता है कि clang ++ का मानना है कि इसने कुछ ऐसा परिभाषित किया है कि यह मानता है कि यह परिभाषित नहीं है , जो आपके सिर को स्पिन करता है, है ना?
सवालों का दूसरा जत्था
- क्या मेरा वर्कअराउंड कानूनी C ++ बिल्कुल सही है, या क्या मैंने केवल एक और g ++ बग की खोज करने का प्रबंधन किया है?
- यदि यह कानूनी है, तो क्या मैंने इसलिए कुछ बुरा क्लैंग ++ बग खोजा?
- या मैं सिर्फ अनफेयर बिहेवियर के डार्क अंडरवर्ल्ड में बहक गया था, इसलिए मैं खुद को ही दोषी मानता हूं?
किसी भी घटना में, मैं किसी ऐसे व्यक्ति का गर्मजोशी से स्वागत करूंगा, जो ज़रूरत पड़ने पर हेडिंग स्पष्टीकरण समझाकर इस खरगोश के छेद से निकलने में मेरी मदद करना चाहता था। : डी
next()
फ़ंक्शन के पैरामीटर के रूप में एक नीरस रूप से बढ़ती संख्या को स्पष्ट रूप से पारित करने की आवश्यकता से बच सकता है , हालांकि मैं वास्तव में यह पता नहीं लगा सकता कि यह कैसे काम करता है। किसी भी घटना में, मैं अपनी समस्या के जवाब के साथ आया हूँ, यहाँ: stackoverflow.com/a/60096865/566849