कोष्ठक के एक निरर्थक सेट के साथ क्लैंग और एमएसवीसी को सदस्य टाइपडिफ़ की घोषणा क्यों पसंद नहीं है?
विचार करें
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 इस त्रुटि का उत्पादन क्यों करते हैं? कौन से कंपाइलर सही हैं?
(मैं विशेष रूप से मानक या किसी भी दोष रिपोर्ट से उद्धरण की तलाश कर रहा हूं।)
जवाब
क्लैंग गलत है: 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)();
घोषित करती है :foo
A
तो एक प्रकार नाम है, जबकि एक पार्स करने का सामना करना पड़ा है डीईसीएल-विनिर्देशक-सेक , यह के भाग के रूप में स्वीकार किया गया डीईसीएल-विनिर्देशक-सेक यदि और केवल यदि कोई पिछले परिभाषित करने प्रकार-विनिर्देशक एक के अलावा अन्य सीवी-क्वालीफायर में डीईसीएल -स्पीसीफायर-सीक ।
विशेष रूप से, इसका मतलब है कि टाइपडिफ घोषणा में
typedef A (foo)();
यहां तक कि अगर वहाँ एक मौजूदा है typedef-नाम foo
, कि foo
typedef घोषणा में नहीं माना जाता है, अर्थात् यह एक के रूप में नहीं माना जाता है प्रकार- नाम का हिस्सा डीईसीएल-विनिर्देशक-सेक की 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
।
क्लैंग और एमएसवीसी दोनों 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();
आप के अर्थ बदल रहे हैं foo
से int
करने के लिए A()
जब आप इसे अंदर redeclare A
। यह basic.scope.class # 2 का उल्लंघन करता है :
एक वर्ग एस में इस्तेमाल किया जाने वाला नाम अपने संदर्भ में उसी घोषणा को संदर्भित करेगा और जब एस के पूर्ण दायरे में पुनर्मूल्यांकन किया जाता है, तो इस नियम के उल्लंघन के लिए कोई निदान आवश्यक नहीं है।
चूंकि यह IFNDR है, सभी संकलक अनुरूप हैं।