गैर-स्थैतिक डेटा सदस्य और नेस्टेड क्लास कंस्ट्रक्टर के इन-क्लास आरंभीकरण का उपयोग करते समय त्रुटि


90

निम्न कोड काफी तुच्छ है और मुझे उम्मीद थी कि इसे ठीक संकलन करना चाहिए।

struct A
{
    struct B
    {
        int i = 0;
    };

    B b;

    A(const B& _b = B())
        : b(_b)
    {}
};

मैंने इस कोड को g ++ संस्करण 4.7.2, 4.8.1, clang ++ 3.2 और 3.3 के साथ परीक्षण किया है। तथ्य यह है कि इस कोड पर जी ++ 4.7.2 segfaults ( http://gcc.gnu.org/bugzilla/show_bug.cgi?id=57770 ) के अलावा, अन्य परीक्षण किए गए कंपाइलर त्रुटि संदेश देते हैं जो अधिक व्याख्या नहीं करते हैं।

जी ++ 4.8.1:

test.cpp: In constructor constexpr A::B::B()’:
test.cpp:3:12: error: constructor required before non-static data member for A::B::i has been parsed
     struct B
            ^
test.cpp: At global scope:
test.cpp:11:23: note: synthesized method constexpr A::B::B()’ first required here 
     A(const B& _b = B())
                       ^

क्लेंग ++ 3.2 और 3.3:

test.cpp:11:21: error: defaulted default constructor of 'B' cannot be used by non-static data member initializer which appears before end of class definition
    A(const B& _b = B())
                    ^

इस कोड को बनाना संभव है और ऐसा लगता है कि इससे कोई फर्क नहीं पड़ना चाहिए। दो विकल्प हैं:

struct B
{
    int i = 0;
    B(){} // using B()=default; works only for clang++
};

या

struct B
{
    int i;
    B() : i(0) {} // classic c++98 initialization
};

क्या यह कोड वास्तव में गलत है या संकलक गलत हैं?


3
मेरा जी ++ 4.7.3 internal compiler error: Segmentation faultइस कोड को कहता है ...
फ्रेड फू

2
(त्रुटि C2864: 'A :: B :: i': केवल स्थिर कॉन्स्टेबल इंटीग्रल डेटा सदस्यों को एक वर्ग के भीतर आरंभीकृत किया जा सकता है) जो VC2010 कहता है। यह आउटपुट g ++ से सहमत है। क्लैंग यह भी कहते हैं, हालांकि यह बहुत कम समझ में आता है। int i = 0जब तक यह नहीं होता तब तक आप किसी चर को किसी डिफॉल्ट में डिफ़ॉल्ट नहीं कर सकते static const int i = 0
कूपर में क्रिस कूपर

@Borgleader: BTW मैं B()एक निर्माता को एक फ़ंक्शन कॉल के रूप में अभिव्यक्ति के बारे में सोचने के प्रलोभन से बचूँगा । आप सीधे एक निर्माता को "कॉल" नहीं करते हैं। इसे विशेष सिंटैक्स के रूप में सोचें जो एक अस्थायी बनाता है B... और निर्माणकर्ता को उस प्रक्रिया के सिर्फ एक हिस्से के रूप में लागू किया जाता है, जो तंत्र के भीतर गहरा होता है।
लाइटवेट रेस ऑर्बिट में

2
हम्म, Bइस काम को करने के लिए लगता है कि एक कंस्ट्रक्टर को जोड़ना gcc 4.7
शफिक याघमोर

7
दिलचस्प है, ए के निर्माता की परिभाषा को ए से बाहर ले जाने से यह काम करने लगता है (जी ++ 4.7); जो "डिफॉल्ट डिफॉल्ट कन्स्ट्रक्टर का उपयोग नहीं किया जा सकता ... वर्ग परिभाषा के अंत से पहले"।
चांदनी

जवाबों:


84

क्या यह कोड वास्तव में गलत है या संकलक गलत हैं?

खैर, न तो। मानक में एक खराबी है - यह कहता है कि दोनों Aको शुरुआती के लिए पार्स करते समय पूरा माना जाता है B::i, और वह B::B()(जिसके लिए शुरुआती का उपयोग करता है B::i) की परिभाषा के भीतर इस्तेमाल किया जा सकता है A। यह स्पष्ट रूप से चक्रीय है। इस पर विचार करो:

struct A {
  struct B {
    int i = (A(), 0);
  };
  A() noexcept(!noexcept(B()));
};

यह एक विरोधाभास है: B::B()परोक्ष है noexceptiff A()फेंक नहीं है, और A()फेंक नहीं है iff B::B()है नहीं noexcept । इस क्षेत्र में कई अन्य चक्र और विरोधाभास हैं।

यह 1360 और 1397 कोर मुद्दों द्वारा ट्रैक किया गया है । विशेष रूप से यह नोट कोर इश्यू 1397 में नोट करें:

शायद इसे संबोधित करने का सबसे अच्छा तरीका यह होगा कि इसे गैर-स्थैतिक डेटा सदस्य इनिशियलाइज़र के लिए अपनी कक्षा के एक डिफॉल्टेड कंस्ट्रक्टर का उपयोग करने के लिए बीमार बनाया जाए।

यह नियम का एक विशेष मामला है जिसे मैंने इस मुद्दे को हल करने के लिए क्लैंग में लागू किया था। क्लैंग का नियम यह है कि किसी वर्ग के लिए एक तयशुदा डिफॉल्ट कंस्ट्रक्टर का उपयोग उस वर्ग के गैर-स्थैतिक डेटा सदस्य इनिशियलाइज़र के पहले नहीं किया जा सकता है। इसलिए क्लैंग यहां एक निदान जारी करता है:

    A(const B& _b = B())
                    ^

... क्योंकि क्लैंग डिफ़ॉल्ट आरंभीकरण करने से पहले डिफ़ॉल्ट तर्क देता है, और इस डिफ़ॉल्ट तर्क के Bलिए पहले से ही आरंभिक रूप से परिभाषित होने के लिए (प्रारंभिक रूप से परिभाषित करने के लिए B::B()) डिफ़ॉल्ट आरंभिकों की आवश्यकता होगी ।


जानकार अच्छा लगा। लेकिन त्रुटि संदेश अभी भी भ्रामक है, क्योंकि कंस्ट्रक्टर वास्तव में "गैर-स्थैतिक डेटा सदस्य इनिशियलाइज़र द्वारा उपयोग किया जाता है" नहीं है।
21

क्या आप इस मुद्दे के साथ एक विशेष अतीत के अनुभव के कारण या मानक (और दोषों की सूची) को ध्यान से पढ़कर जानते हैं? इसके अलावा, +1।
कॉर्नस्टैल्स

इस विस्तृत उत्तर के लिए +1। तो बाहर का रास्ता क्या होगा? कुछ प्रकार के "2-चरण वर्ग पार्सिंग", जहां आंतरिक कक्षाओं पर निर्भर रहने वाले बाहरी वर्ग के सदस्यों की पार्सिंग में देरी होती है जब तक कि आंतरिक कक्षाएं पूरी तरह से नहीं बन जाती हैं?
टेम्प्लेटेक्स

4
@aschepler हाँ, यहाँ निदान बहुत अच्छा नहीं है। मैंने उसके लिए llvm.org/PR16550 फाइल किया है।
रिचर्ड स्मिथ

@ कॉर्नस्टालक्स ने इस मुद्दे की खोज की, जबकि मैं क्लैंग में गैर-स्थैतिक डेटा सदस्यों के लिए इनिशियलाइज़र लागू कर रहा था।
रिचर्ड स्मिथ

0

शायद यह समस्या है:

§12.1 5. एक डिफॉल्ट कंस्ट्रक्टर जिसे डिफॉल्ट किया गया है और डिलीट के रूप में डिफाइन नहीं किया गया है, इसे तब स्पष्ट रूप से परिभाषित किया जाता है, जब वह अपने वर्ग प्रकार (1.8) का ऑब्जेक्ट बनाने के लिए इस्तेमाल किया जाता है (3.2) या जब इसकी पहली घोषणा के बाद इसे डिफॉल्ट रूप से डिफॉल्ट किया जाता है

इसलिए, डिफ़ॉल्ट कंस्ट्रक्टर पहली बार देखे जाने पर उत्पन्न होता है, लेकिन लुकअप विफल हो जाएगा क्योंकि A पूरी तरह से परिभाषित नहीं है और A के अंदर B इसलिए नहीं मिला है।


मुझे उस "इसलिए" पर यकीन नहीं है। स्पष्ट रूप B bसे कोई समस्या नहीं है, और स्पष्ट तरीके / स्पष्ट रूप से घोषित निर्माणकर्ता को खोजना Bकोई समस्या नहीं है। इसलिए यह देखना अच्छा होगा कि क्यों लुकअप अलग तरीके से यहां आगे बढ़ना चाहिए ताकि "इस तरह से Bअंदर Aनहीं पाया जाए" सिर्फ इस एक मामले में, लेकिन दूसरों को नहीं, इससे पहले कि हम इस कारण से कोड को अवैध घोषित कर सकें।
चांदनी

मुझे मानक में ऐसे शब्द मिले जो वर्ग परिभाषा के दौरान कक्षा की परिभाषा को पूर्ण माना जाता है, जिसमें नेस्टेड कक्षाओं के भीतर भी शामिल है। मैंने संदर्भ को रिकॉर्ड करने की जहमत नहीं उठाई क्योंकि यह प्रासंगिक नहीं लगता था।
मार्क बी

@moonshadow: इस कथन में स्पष्ट रूप से डिफॉल्ट रूप से इस्तेमाल किए जाने वाले डिफॉल्टर्स को परिभाषित किया गया है। पहली घोषणा के बाद स्पष्ट रूप से परिभाषित किया गया है। और B b एक कंस्ट्रक्टर को कॉल नहीं करता है, A कॉल कंस्ट्रक्टर का B
fscan

तो इस तरह से कुछ भी समस्या थे, कोड अभी भी अमान्य हो अगर आप निकालना चाहते हैं =0से i = 0;। लेकिन इसके बिना =0, कोड वैध है और आपको एक भी संकलक नहीं मिलेगा जो कि B()परिभाषा के भीतर उपयोग करने के बारे में शिकायत करता है A
21
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.