C ++ संकलन समय काउंटर, पुनरीक्षित


28

टी एल; डॉ

इससे पहले कि आप इस पूरी पोस्ट को पढ़ने का प्रयास करें, यह जानिए:

  1. प्रस्तुत मुद्दे का हल खुद ही मिल गया है , लेकिन मैं अभी भी यह जानने के लिए उत्सुक हूं कि क्या विश्लेषण सही है;
  2. मैंने समाधान को एक fameta::counterवर्ग में पैक किया है जो कुछ शेष क्वैरकों को हल करता है। आप इसे जीथब पर पा सकते हैं ;
  3. आप इसे काम पर देख सकते हैं ।

यह सब कब प्रारंभ हुआ

चूंकि फिलिप रोसेन ने खोज / आविष्कार किया था, 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()हमेशा रिटर्न होता है। थोड़ा प्रयोग करने के बाद, जी ++ के साथ कम से कम समस्या यह प्रतीत होती है कि एक बार कंपाइलर फंक्शंस डिफॉल्ट मापदंडों का मूल्यांकन करने के बाद पहली बार फ़ंक्शन को कॉल करता है, किसी भी बाद में कॉल। उन कार्यों ने डिफ़ॉल्ट मापदंडों के पुनर्मूल्यांकन को ट्रिगर नहीं किया है, इस प्रकार कभी भी नए कार्यों को तात्कालिक नहीं किया जाता है लेकिन हमेशा पहले के तात्कालिक लोगों का जिक्र किया जाता है।


पहले सवाल

  1. क्या आप वास्तव में मेरे इस निदान से सहमत हैं?
  2. यदि हाँ, तो क्या यह नया व्यवहार मानक द्वारा अनिवार्य है? क्या पिछला वाला बग था?
  3. यदि नहीं, तो समस्या क्या है?

उपरोक्त को ध्यान में रखते हुए, मैं चारों ओर एक काम के साथ आया: प्रत्येक को चिह्नित करें 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 ++ का मानना ​​है कि इसने कुछ ऐसा परिभाषित किया है कि यह मानता है कि उसने परिभाषित नहीं किया है

तो, ऐसा प्रतीत होता है कि clang ++ का मानना ​​है कि इसने कुछ ऐसा परिभाषित किया है कि यह मानता है कि यह परिभाषित नहीं है , जो आपके सिर को स्पिन करता है, है ना?


सवालों का दूसरा जत्था

  1. क्या मेरा वर्कअराउंड कानूनी C ++ बिल्कुल सही है, या क्या मैंने केवल एक और g ++ बग की खोज करने का प्रबंधन किया है?
  2. यदि यह कानूनी है, तो क्या मैंने इसलिए कुछ बुरा क्लैंग ++ बग खोजा?
  3. या मैं सिर्फ अनफेयर बिहेवियर के डार्क अंडरवर्ल्ड में बहक गया था, इसलिए मैं खुद को ही दोषी मानता हूं?

किसी भी घटना में, मैं किसी ऐसे व्यक्ति का गर्मजोशी से स्वागत करूंगा, जो ज़रूरत पड़ने पर हेडिंग स्पष्टीकरण समझाकर इस खरगोश के छेद से निकलने में मेरी मदद करना चाहता था। : डी



2
जैसा कि मुझे याद है कि मानक समिति के लोगों का स्पष्ट इरादा होता है कि वे किसी भी प्रकार, आकार या रूप के संकलन-समय निर्माणों को अस्वीकार करें जो कि हर बार (काल्पनिक रूप से) मूल्यांकन किए गए सटीक परिणाम नहीं देते हैं। तो यह एक कंपाइलर बग हो सकता है, यह एक "बीमार-गठन, कोई निदान की आवश्यकता नहीं" मामला हो सकता है या यह कुछ मानक छूट गया हो सकता है। फिर भी यह "मानक की भावना" के खिलाफ जाता है। मुझे क्षमा करें। मुझे संकलन समय काउंटर भी पसंद आया होगा।
बोलाव

@HolyBlackCat मुझे स्वीकार करना चाहिए कि मुझे उस कोड के आसपास अपना सिर लाने में बहुत मुश्किल हो रही है। ऐसा लगता है कि यह next()फ़ंक्शन के पैरामीटर के रूप में एक नीरस रूप से बढ़ती संख्या को स्पष्ट रूप से पारित करने की आवश्यकता से बच सकता है , हालांकि मैं वास्तव में यह पता नहीं लगा सकता कि यह कैसे काम करता है। किसी भी घटना में, मैं अपनी समस्या के जवाब के साथ आया हूँ, यहाँ: stackoverflow.com/a/60096865/566849
Fabio A.

@FabioA। मैं भी पूरी तरह से उस जवाब को नहीं समझता। उस सवाल को पूछने के बाद, मुझे एहसास हुआ कि मैं कॉन्स्ट्रेक्स काउंटर को फिर से नहीं छूना चाहता।
होलीब्लैकैट

हालांकि यह एक मजेदार विचार है, कोई है जो वास्तव में उस कोड का उपयोग करता है, उसे बहुत उम्मीद है कि यह सी ++ के भविष्य के संस्करणों में काम नहीं करेगा, है ना? उस अर्थ में, परिणाम एक बग के रूप में खुद को परिभाषित करता है।
अज़ीथ

जवाबों:


5

आगे की जांच के बाद, यह पता चलता है कि एक मामूली संशोधन मौजूद है next()जो फ़ंक्शन के लिए किया जा सकता है , जो 7.0.0 से ऊपर क्लैंग ++ संस्करणों पर कोड को ठीक से काम करता है, लेकिन यह अन्य सभी क्लैंग ++ संस्करणों के लिए काम करना बंद कर देता है।

निम्नलिखित कोड पर एक नज़र डालें, मेरे पिछले समाधान से लिया गया है।

template <int N>
constexpr int next(int R = writer<N, reader(0, slot<N>())+1>::value) {
    return R;
}

यदि आप इस पर ध्यान देते हैं, तो इसका शाब्दिक अर्थ क्या है कि इससे जुड़े मूल्य को पढ़ने की कोशिश करें slot<N>, इसमें 1 जोड़ें और फिर इस नए मूल्य को उसी के साथ जोड़ दें slot<N>

जब slot<N>कोई संबद्ध मूल्य नहीं होता है, तो इसके साथ जुड़े मूल्य slot<Y>को इसके बजाय पुनर्प्राप्त किया जाता है, जो कि संबंधित मूल्य Yसे कम उच्चतम सूचकांक है ।Nslot<Y>

उपरोक्त कोड के साथ समस्या यह है कि, भले ही यह g ++, clang ++ (सही में, मैं कहूं) पर काम करता है, reader(0, slot<N>()) स्थायी रूप से जो कुछ भी slot<N>संबंधित मूल्य नहीं होने पर वापस लौटा देता है । बदले में, इसका मतलब है कि सभी स्लॉट आधार मूल्य के साथ प्रभावी रूप से जुड़े हुए हैं 0

समाधान उपरोक्त कोड को इस में बदलना है:

template <int N>
constexpr int next(int R = writer<N, reader(0, slot<N-1>())+1>::value) {
    return R;
}

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

यूरेका!

यह क्लैंग ++ वर्जन <= 7.0.0 को तोड़ता है, हालांकि।

निष्कर्ष

ऐसा लगता है कि मेरे द्वारा पोस्ट किए गए मूल समाधान में एक वैचारिक बग है, जैसे:

  • g ++ में क्वर्क / बग / विश्राम है जो मेरे समाधान के बग के साथ रद्द हो जाता है और अंततः कोड को फिर भी काम करता है।
  • clang ++ वर्जन> 7.0.0 सख्त हैं और मूल कोड में बग को पसंद नहीं करते हैं।
  • clang ++ वर्जन <= 7.0.0 में एक बग है जो सही किए गए समाधान को काम नहीं करता है।

सभी को समेटते हुए, निम्न कोड g ++ और clang ++ के सभी संस्करणों पर काम करता है।

#if !defined(__clang_major__) || __clang_major__ > 7
template <int N>
constexpr int next(int R = writer<N, reader(0, slot<N-1>())+1>::value) {
    return R;
}
#else
template <int N>
constexpr int next(int R = writer<N, reader(0, slot<N>())+1>::value) {
    return R;
}
#endif

कोड के रूप में भी msvc के साथ काम करता है। का उपयोग करते समय आईसीसी संकलक SFINAE ट्रिगर नहीं करता decltype(counter(slot<N>())), में सक्षम नहीं होने के बारे में शिकायत करने के लिए पसंद करते हैं deduce the return type of function "counter(slot<N>)"क्योंकि it has not been defined। मेरा मानना ​​है कि यह एक बग है , जिसके प्रत्यक्ष परिणाम पर SFINAE करके आसपास काम किया जा सकता है counter(slot<N>)। यह अन्य सभी संकलक पर भी काम करता है, लेकिन जी ++ बहुत कष्टप्रद चेतावनियों के एक प्रचुर मात्रा में थूकने का फैसला करता है जिसे बंद नहीं किया जा सकता है। तो, इस मामले में भी, #ifdefबचाव के लिए आ सकता है।

सबूत godbolt पर है नीचे screnshotted,।

यहाँ छवि विवरण दर्ज करें


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