कोष्ठक के एक निरर्थक सेट के साथ क्लैंग और एमएसवीसी को सदस्य टाइपडिफ़ की घोषणा क्यों पसंद नहीं है?

Dec 05 2020

विचार करें

using foo = int;

struct A {
    typedef A (foo)();
};

GCC और ICC स्निपेट को स्वीकार करते हैं, जबकि Clang और MSVC इसे अस्वीकार करते हैं। Clang का एरर मैसेज है

<source>:4:15: error: function cannot return function type 'void ()'
    typedef A (foo)();
              ^
<source>:4:13: error: typedef name must be an identifier
    typedef A (foo)();
            ^
2 errors generated.

और MSVC का कहना है

<source>(4,15): error C2091: function returns function
    typedef A (foo)();
              ^

( लाइव डेमो )

क्लैंग और MSVC इस त्रुटि का उत्पादन क्यों करते हैं? कौन से कंपाइलर सही हैं?

(मैं विशेष रूप से मानक या किसी भी दोष रिपोर्ट से उद्धरण की तलाश कर रहा हूं।)

जवाब

2 dfrib Dec 06 2020 at 20:13

क्लैंग गलत है: fooटाइपसेफ घोषणा में Aनेमस्पेस-स्कोप टाइपडेफ-नाम का उल्लेख नहीं करता है foo

मानक नियमों, एनक्लोजिंग नेमस्पेस / स्कोप उर्फ ​​घोषणा

using foo = int;

एक लाल हेरिंग है; वर्ग की कथात्मक दायरे के भीतर Aयह नाम द्वारा आच्छादित हो जाएगा की घोषणा की मेंA

#include <type_traits>

using foo = int;
struct A {
    using foo = char;
    foo x;
};

static_assert(std::is_same_v<foo, int>,"");
static_assert(std::is_same_v<A::foo, char>,"");
static_assert(std::is_same_v<decltype(A::x), char>,"");

यहाँ होने वाली कुंजी [dcl.spec] / 3 [ बल मेरा] के अनुसार, घोषणा क्षेत्र के भीतर नाम typedef A (foo)(); घोषित करती है :fooA

तो एक प्रकार नाम है, जबकि एक पार्स करने का सामना करना पड़ा है डीईसीएल-विनिर्देशक-सेक , यह के भाग के रूप में स्वीकार किया गया डीईसीएल-विनिर्देशक-सेक यदि और केवल यदि कोई पिछले परिभाषित करने प्रकार-विनिर्देशक एक के अलावा अन्य सीवी-क्वालीफायर में डीईसीएल -स्पीसीफायर-सीक

विशेष रूप से, इसका मतलब है कि टाइपडिफ घोषणा में

typedef A (foo)();

यहां तक कि अगर वहाँ एक मौजूदा है typedef-नाम foo , कि footypedef घोषणा में नहीं माना जाता है, अर्थात् यह एक के रूप में नहीं माना जाता है प्रकार- नाम का हिस्सा डीईसीएल-विनिर्देशक-सेक की typedef A (foo)(), के रूप में Aपहले से ही यह करने के लिए पिछले का सामना करना पड़ा रहा है, और Aहै एक वैध परिभाषित-प्रकार-विनिर्देशक । इस प्रकार, मूल उदाहरण:

using foo = int;

struct A {
    typedef A (foo)();
};

इसे कम किया जा सकता है:

// (i)
struct A {
    typedef A (foo)();  // #1
};

जो में टाइप किए गए नाम की घोषणा करता fooहै A( A::foo), जहां नाम के चारों ओर परांठा निरर्थक है, और # 1 पर टाइप किए गए घोषणा को इसी तरह लिखा जा सकता है

// (ii)
struct A {
    typedef A foo();  // #1
};

और इसी तरह एक उपनाम घोषणा ( [dcl.typedef] / 2 ) का उपयोग करके पेश किया जा सकता है :

// (iii)
struct A {
    using foo = A();
};

(i), (ii)और (iii)GCC और Clang दोनों द्वारा स्वीकार किए जाते हैं।

अंत में, हम ध्यान दें कि क्लैंग निम्नलिखित कार्यक्रम को स्वीकार करता है:

using foo = int;
struct A {
    typedef A foo();
    using bar = A();
};

static_assert(std::is_same_v<A::foo, A::bar>,"");

और यह कि ओपी के उदाहरण का मूल मुद्दा निश्चित रूप से एक क्लैंग बग है, जहां क्लैंग [dcl.spec] / 3 का पालन करने में विफल रहता है और बाहरी-स्कोप टाइप- बीफ़ -नाम की व्याख्या करता है, fooजो कि घोषणा-विनिर्देश-सीक के हिस्से के रूप में है इनर-स्कोप टाइपेडिफ घोषणा, केवल उस मामले के लिए जहां बाद वाले ने परांठे में छाया हुआ नाम लपेटा है foo

3 ecatmur Dec 05 2020 at 22:38

क्लैंग और एमएसवीसी दोनों typedefनिर्दिष्टकर्ता को अनदेखा कर रहे हैं और घोषणा को एक निर्माता के रूप में पढ़ रहे हैं (जो कि, Aकंस्ट्रक्टर का नाम है) पैरामीटर प्रकारों को स्वीकार कर रहा है (foo)(जो है (int)) और अनुगामी कोष्ठकों द्वारा दर्शाए गए फ़ंक्शन प्रकार को "वापस" करना है ()

हां, कंस्ट्रक्टर्स के पास रिटर्न प्रकार नहीं हैं; लेकिन अगर वे किया था वापसी प्रकार है वे वापसी प्रकार होता है Aतो अतिरिक्त, ()बनाता है अंत में इन compilers लगता है कि तुम अब वापसी प्रकार समारोह प्रकार के साथ एक निर्माता है A()

यह ध्यान देने योग्य है कि निम्नलिखित "समान" घोषणाओं में समान त्रुटि संदेश हैं:

A (foo)();
typedef ~A(foo)();

इसके अलावा, staticहम MSVC से एक शानदार त्रुटि संदेश प्राप्त कर सकते हैं:

A static (int)();
error C2574: '(__cdecl *A::A(int))(void)': cannot be declared static

वर्कअराउंड के लिए: क्लैंग (लेकिन MSVC नहीं) के तहत आप स्पेसर typedefको दाईं ओर ले जा सकते हैं , या विस्तृत प्रकार के स्पेसियर का उपयोग कर सकते हैं:

A typedef (foo)();
typedef struct A (foo)();

सभी कंपाइलरों के अंतर्गत आप कोष्ठक हटा या जोड़ सकते हैं:

typedef A foo();
typedef A ((foo))();

और आप हमेशा एक अन्य उपनाम के लिए अद्यतन कर सकते हैं:

using foo = A();
cigien Dec 05 2020 at 21:49

आप के अर्थ बदल रहे हैं fooसे intकरने के लिए A()जब आप इसे अंदर redeclare A। यह basic.scope.class # 2 का उल्लंघन करता है :

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

चूंकि यह IFNDR है, सभी संकलक अनुरूप हैं।