क्या मैं C ++ में एक स्वायत्त `स्व` सदस्य प्रकार लागू कर सकता हूं?


101

C ++ में PHP के कीवर्ड के बराबर की कमी है , जो कि संलग्नक वर्ग के प्रकार का मूल्यांकन करता है।self

प्रति वर्ग के आधार पर इसे नकली बनाना काफी आसान है:

struct Foo
{
   typedef Foo self;
};

लेकिन मुझे Fooफिर से लिखना पड़ा । हो सकता है कि मुझे एक दिन यह गलत लगे और एक मूक बग पैदा कर दे।

क्या मैं decltypeइस कार्य को "स्वायत्त" करने के लिए और दोस्तों के कुछ संयोजन का उपयोग कर सकता हूं ? मैंने पहले ही प्रयास कर लिया है लेकिन thisउस स्थान पर मान्य नहीं है:

struct Foo
{
   typedef decltype(*this) self;
};

// main.cpp:3:22: error: invalid use of 'this' at top level
//     typedef decltype(*this) self;

(मैं बराबर के बारे में चिंता करने वाला नहीं हूं static, जो समान है लेकिन देर से बाध्यकारी के साथ।)


9
this_tसंभवतया नियमित C ++ नामकरण के साथ अधिक संरेखित किया जाएगा।
बार्टेक बैनचेविच

3
@BartekBanachewicz: या this_type
PlasmaHH

10
@Petetorian, मुझे याद नहीं है कि यह एक प्रस्ताव था या नहीं, लेकिन किसी ने सुझाव दिया auto()और ~auto()ctors / dtors के लिए। बस इतना ही कहना है कि बहुत रोचक है। अगर उस उद्देश्य के लिए उपयोग किया जाता है, तो शायद typedef auto self;, लेकिन यह मुझे थोड़ा अजीब लगता है।
क्रिस

11
ईमानदारी से, अगर मैं इसे संभव बनाने के लिए वाक्यविन्यास का सुझाव देने जा रहा था, तो यह संभवतः decltype(class), एक decltype(struct)समकक्ष के साथ होगा । यह केवल autoएक विशिष्ट संदर्भ में की तुलना में अधिक स्पष्ट है और मुझे इसके आधार पर भाषा में फिटिंग के साथ कोई समस्या नहीं दिखती है decltype(auto)
क्रिस

11
चूँकि आप त्रुटियों से बचना चाहते हैं, इसलिए आप static_assert के साथ एक डमी मेंबर फंक्शन सेट कर सकते हैं, जैसे void _check() { static_assert(std::is_same<self&, decltype(*this)>::value, "Correct your self type"); }क्लास टेम्प्लेट के साथ काम नहीं करते, हालांकि ...
मिलेनियमबग

जवाबों:


39

यहां बताया गया है कि आप फू के प्रकार को दोहराए बिना इसे कैसे कर सकते हैं:

template <typename...Ts>
class Self;

template <typename X, typename...Ts>
class Self<X,Ts...> : public Ts...
{
protected:
    typedef X self;
};

#define WITH_SELF(X) X : public Self<X>
#define WITH_SELF_DERIVED(X,...) X : public Self<X,__VA_ARGS__>

class WITH_SELF(Foo)
{
    void test()
    {
        self foo;
    }
};

यदि आप उससे प्राप्त करना चाहते हैं Fooतो आपको WITH_SELF_DERIVEDनिम्नलिखित तरीके से मैक्रो का उपयोग करना चाहिए :

class WITH_SELF_DERIVED(Bar,Foo)
{
    /* ... */
};

आप जितनी चाहें उतनी बेस क्लास भी कर सकते हैं।

class WITH_SELF(Foo2)
{
    /* ... */
};

class WITH_SELF_DERIVED(Bar2,Foo,Foo2)
{
    /* ... */
};

मैंने इसे gcc 4.8 पर काम करने के लिए सत्यापित किया है और 3.4 को क्लैंग किया है।


18
मुझे लगता है कि उत्तर "नहीं, लेकिन राल्फ कर सकता है!" ;)
ऑर्बिट

3
यह कैसे किसी भी तरह से केवल टाइपराइफ को वहां लगाने से बेहतर है? और भगवान, आपको टंकण की आवश्यकता भी क्यों होगी? क्यों?
मील्स राउत

7
@ माइल्सआउट यह सवाल का जवाब है, जवाब का नहीं। सॉफ्टवेयर विकास (और विशेष रूप से रखरखाव) में कई मामलों में यह कोड में अतिरेक से बचने के लिए सहायक होता है, ताकि एक जगह पर कुछ बदलने से आपको कोड को दूसरी जगह बदलने की आवश्यकता न हो। इस मामले में autoऔर इस के पूरे बिंदु decltypeया है self
राल्फ टंडेट्ज़की

1
template<typename T>class Self{protected: typedef T self;}; class WITH_SELF(Foo) : public Bar, private Baz {};सरल होता और वंशानुक्रम पर अधिक सटीक नियंत्रण की अनुमति देता - किसी भी कारण से?
एकॉनगुआ

@mmmmmmmm, यदि आपने "डोंट रिपीट योरसेल्फ" सिद्धांत की गहराई से सराहना नहीं की है, तो संभावना है कि आपने अभी तक पर्याप्त या गंभीरता से कोडिंग नहीं की है। यह "अव्यवस्था" (इससे बहुत दूर, वास्तव में) एक अयोग्य भाषा सुविधा (या मिसफिट, यहां तक ​​कि कुछ सख्त उपायों द्वारा कमी) के बारे में बात करने के संदर्भ में काफी सुरुचिपूर्ण है।
एस.जे.

38

एक संभावित समाधान (जैसा कि आपको अभी भी एक बार लिखना है):

template<typename T>
struct Self
{
protected:
    typedef T self;
};

struct Foo : public Self<Foo>
{
    void test()
    {
        self obj;
    }
};

अधिक सुरक्षित संस्करण के लिए हम आश्वासन दे सकते हैं कि Tवास्तव में इससे प्राप्त होता है Self<T>:

Self()
{
    static_assert(std::is_base_of<Self<T>, T>::value, "Wrong type passed to Self");
}

ध्यान दें कि एक static_assertसदस्य फ़ंक्शन के अंदर जांच करने का एकमात्र तरीका है, जैसा std::is_base_ofकि पूर्ण होने के लिए पारित प्रकार ।


4
typenameटाइपिफ़ में कोई ज़रूरत नहीं है । और चूंकि यह अतिरेक की संख्या को कम नहीं करता है, मुझे नहीं लगता कि यह एक व्यवहार्य विकल्प है।
कोनराड रुडोल्फ

Fooनाम को दोहराने की ठीक यही समस्या है ।
बार्टेक बानचेविच

6
यह है मामूली मूल दृष्टिकोण की तुलना में बेहतर है, हालांकि, के बाद से पुनरावृत्ति बहुत करीब एक साथ है। सवाल का हल नहीं, लेकिन सबसे अच्छा वर्कअराउंड में एक योग्य प्रयास के लिए +1।
in पर ऑर्बिट

4
मैंने उस हल का एक-दो बार उपयोग किया, और इसमें एक BAD चीज है: जब बाद में प्राप्त होता है Foo, तो आपको या तो: (1) T को पत्ती-वंश तक फैलाना होगा, या (2) कई बार सेल्फी से याद रखना होगा , या (3) स्वीकार करते हैं कि सभी बच्चे आधार होने की बात करते हैं .. प्रयोग करने योग्य, लेकिन व्याख्यात्मक।
क्वेटज़ालकोट

@quetzalcoatl: जब से मैं selfबजाय दोहराने की कोशिश कर रहा हूँ static, यह कोई समस्या नहीं है।
ऑर्बिट

33

आप एक नियमित वर्ग घोषणा के बजाय एक मैक्रो का उपयोग कर सकते हैं, जो आपके लिए ऐसा करेगा।

#define CLASS_WITH_SELF(X) class X { typedef X self;

और फिर उपयोग करें

CLASS_WITH_SELF(Foo) 
};

#define END_CLASS }; शायद पठनीयता में मदद मिलेगी।


आप @ Paranaix का Selfउपयोग कर सकते हैं और इसका उपयोग कर सकते हैं (यह वास्तव में हैक होने लगता है)

#define WITH_SELF(X) X : public Self<X>

class WITH_SELF(Foo) {
};

18
EWWWW END_CLASS यह पूरी तरह अनावश्यक है।
पिल्ला

31
@DeadMG मुझे लगता है कि कुछ लोगों को अधिक स्थिरता पसंद हो सकती है; आखिरकार, पहला मैक्रो उपयोग समाप्त नहीं होता है {, इसलिए }"फांसी" है, जो पाठ संपादक शायद नापसंद करेंगे।
बार्टेक बैनाचविच

6
अच्छा विचार है, लेकिन भले ही मैं मूल रूप से मैक्रोज़ का विरोध नहीं कर रहा हूं, मैं केवल यहां इसके उपयोग को स्वीकार करूंगा यदि यह सी ++ स्कोपिंग की नकल करता है, अर्थात यदि यह प्रयोग करने योग्य था CLASS_WITH_SELF(foo) { … };- और मुझे लगता है कि इसे प्राप्त करना असंभव है।
कोनराड रुडोल्फ

2
@KonradRudolph मैंने ऐसा करने का एक तरीका जोड़ा है। ऐसा नहीं है कि मैं इसे पसंद करता हूं, सिर्फ पूर्णता के लिए
बार्टेक बैनाचेविच

1
उस दृष्टिकोण के साथ कुछ समस्याएं हैं, हालांकि। पहला आपको वर्ग को आसानी से विरासत में लाने की अनुमति नहीं देता है (जब तक कि आप किसी अन्य मैक्रो पैरामीटर (एस) का उपयोग नहीं करते हैं), और दूसरे में उस से विरासत में मिली सभी समस्याएं हैं Self
बार्टेक बैनविचविच

31

मेरे पास कोई सकारात्मक सबूत नहीं है लेकिन मुझे लगता है कि यह असंभव है। निम्नलिखित विफल रहता है - आपके प्रयास के समान कारण के लिए - और मुझे लगता है कि हम सबसे दूर हो सकते हैं:

struct Foo {
    auto self_() -> decltype(*this) { return *this; }

    using self = decltype(self_());
};

अनिवार्य रूप से, यह दर्शाता है कि वह गुंजाइश है जिस पर हम अपने टाइपिडेफ़ की घोषणा करना चाहते हैं, उसकी कोई पहुँच नहीं है (यह प्रत्यक्ष या अप्रत्यक्ष है) this, और कक्षा के प्रकार या नाम के लिए कोई अन्य (संकलक स्वतंत्र) तरीका नहीं है।


4
क्या यह संभवतः C ++ 1y की वापसी प्रकार की कटौती के साथ होगा?
DYP

4
@dyp मेरे उत्तर के उद्देश्य से जो कुछ भी नहीं बदलेगा। यहां त्रुटि ट्रेलिंग रिटर्न प्रकार में नहीं है, यह मंगलाचरण में है।
कोनराड रुडोल्फ

1
@quetzalcoatl: की पारी decltypeएक असंबंधित प्रसंग है, इसलिए सदस्य समारोह को लागू करना समस्या नहीं है (यह प्रयास नहीं किया जाएगा)
हल्की दौड़ ऑर्बिट में

1
@TomKnapen इसे क्लैंग के साथ आज़माएं, और यह विफल हो जाएगा। तथ्य यह है कि यह जीसीसी द्वारा स्वीकार किया जाता है एक बग है, जहां तक ​​मुझे पता है।

4
FWIW, एक गैर-स्थैतिक डेटा सदस्य struct S { int i; typedef decltype(i) Int; };होने के बावजूद काम करता iहै। यह काम करता है क्योंकि decltypeएक विशेष अपवाद है जहां एक सरल नाम का अभिव्यक्ति के रूप में मूल्यांकन नहीं किया जाता है। लेकिन मैं इस संभावना का उपयोग करने के किसी भी तरीके के बारे में नहीं सोच सकता जो सवाल का जवाब देता है।

21

जीसीसी और क्लैंग दोनों में जो काम करता है वह एक टाइपडिफ बनाने के लिए thisहै thisजो फ़ंक्शन टाइपिंगफ के ट्रेलिंग-रिटर्न-प्रकार में उपयोग करके संदर्भित करता है । चूंकि यह एक स्थिर सदस्य फ़ंक्शन की घोषणा नहीं है, इसलिए का उपयोग thisसहन किया जाता है। फिर आप परिभाषित करने के लिए उस टाइपपैड का उपयोग कर सकते हैं self

#define DEFINE_SELF() \
    typedef auto _self_fn() -> decltype(*this); \
    using self = decltype(((_self_fn*)0)())

struct Foo {
    DEFINE_SELF();
};

struct Bar {
    DEFINE_SELF();
};

दुर्भाग्य से, मानक के एक सख्त पढ़ने का कहना है कि यह भी मान्य नहीं है। क्लैंग क्या जांचता है कि thisइसका उपयोग स्थिर सदस्य फ़ंक्शन की परिभाषा में नहीं किया गया है। और यहाँ, यह वास्तव में नहीं है। GCC को कोई आपत्ति नहीं है यदि thisफ़ंक्शन के प्रकार की परवाह किए बिना ट्रेलिंग-रिटर्न-प्रकार में उपयोग किया जाता है, तो यह staticसदस्य कार्यों के लिए भी अनुमति देता है । हालांकि, वास्तव में मानक की आवश्यकता होती है जो कि thisगैर-स्थैतिक सदस्य फ़ंक्शन (या गैर-स्थैतिक डेटा सदस्य प्रारंभिक गुण) की परिभाषा के बाहर उपयोग नहीं की जाती है । इंटेल इसे सही पाता है और इसे खारिज कर देता है।

मान लीजिये:

  • this केवल गैर-स्थैतिक डेटा सदस्य आरंभिक और गैर-स्थैतिक सदस्य फ़ंक्शन ([expr.prim.general] 5/5) में अनुमत है,
  • गैर-स्थैतिक डेटा सदस्यों को उनके प्रकार का आरंभिक ([dcl.spec.auto] p5) से घटाया नहीं जा सकता है,
  • गैर-स्थैतिक सदस्य फ़ंक्शन को केवल फ़ंक्शन कॉल ([expr.ref] 4) के संदर्भ में एक अयोग्य नाम से संदर्भित किया जा सकता है
  • गैर-स्थैतिक सदस्य फ़ंक्शंस को केवल अयोग्य नाम से बुलाया जा सकता है, यहां तक ​​कि thisअनवील किए गए संदर्भों में भी, जब इस्तेमाल किया जा सकता है ([over.call.func] p3)
  • गैर-स्थैतिक सदस्य फ़ंक्शन के लिए योग्य नाम या सदस्य पहुंच के संदर्भ को परिभाषित किए जाने वाले प्रकार के संदर्भ की आवश्यकता होती है

मुझे लगता है कि मैं निर्णायक रूप से कह सकता हूं कि selfकिसी भी तरह, कहीं न कहीं, प्रकार के नाम को शामिल किए बिना लागू करने का कोई तरीका नहीं है ।

संपादित करें : मेरे पहले के तर्क में दोष है। "गैर-स्थैतिक सदस्य फ़ंक्शंस को केवल अयोग्य नाम से बुलाया जा सकता है, यहां तक ​​कि बिना किसी संदर्भ में भी, जब इसका इस्तेमाल किया जा सकता है ([over.call.func] p3)," गलत है। यह वास्तव में क्या कहता है

यदि कीवर्ड this(9.3.2) दायरे में है और वर्ग T, या व्युत्पन्न वर्ग को संदर्भित करता है T, तो निहित ऑब्जेक्ट तर्क है (*this)। यदि कीवर्ड thisस्कोप में नहीं है या किसी अन्य वर्ग को संदर्भित करता है, तो प्रकार की एक वंचित वस्तु Tनिहित ऑब्जेक्ट तर्क बन जाती है। यदि तर्क सूची को एक वंचित वस्तु द्वारा संवर्धित किया जाता है और अधिभार संकल्प गैर-स्थैतिक सदस्य कार्यों में से एक का चयन करता है T, तो कॉल अ -गठन होता है।

एक स्थिर सदस्य फ़ंक्शन के अंदर, thisप्रकट नहीं हो सकता है, लेकिन यह अभी भी मौजूद है।

हालांकि, टिप्पणियों के अनुसार, एक स्थिर सदस्य समारोह के अंदर, के परिवर्तन f()करने के लिए (*this).f()नहीं किया जा जाएगा, और यह नहीं किया जाता है कि, तो [expr.call] p1 का उल्लंघन किया जाता है:

[...] सदस्य फ़ंक्शन कॉल के लिए, पोस्टफ़िक्स एक्सप्रेशन एक अंतर्निहित (9.3.1, 9.4) या स्पष्ट वर्ग सदस्य एक्सेस (5.2.5) होगा जिसका [...]

जैसा कि कोई सदस्य पहुंच नहीं होगा। तो यह भी काम नहीं करेगा।


मुझे लगता है कि [class.mfct.non-static] / 3 का कहना है कि _self_fn_1()यह "रूपांतरित" है (*this)._self_fn_1()। सुनिश्चित नहीं हैं कि अगर यह अवैध है, हालांकि।
dyp

@dyp यह कहता है " Xएक संदर्भ में कक्षा के एक सदस्य में thisउपयोग किया जाता है, जहां इसका उपयोग किया जा सकता है", इसलिए मुझे नहीं लगता कि परिवर्तन किया जाता है।

1
लेकिन तब यह न तो एक अंतर्निहित और न ही स्पष्ट वर्ग के सदस्य का उपयोग है ..? [expr.call] / 1 "सदस्य फ़ंक्शन कॉल के लिए, पोस्टफ़िक्स एक्सप्रेशन एक अंतर्निहित या स्पष्ट वर्ग सदस्य एक्सेस होगा [...]"
dyp

(मेरा मतलब है, आपके पास क्या होता है auto _self_fn_1() -> decltype(*this); auto _self_fn_1() const -> decltype(*this);?)
dyp

@dyp [expr.call] / 1 एक अच्छा बिंदु है, मुझे करीब से देखना होगा। constओवरलोड के बारे में , हालांकि: यह एक समस्या नहीं है। 5.1p3 को विशेष रूप से स्थैतिक सदस्य कार्यों पर लागू करने के लिए संशोधित किया गया है, और कहते हैं कि / / (बिना ) के प्रकार thisहै , क्योंकि की घोषणा में नहीं है । Foo*Bar*constconst_self_fn_2

17
#define SELF_CHECK( SELF ) void self_check() { static_assert( std::is_same< typename std::decay<decltype(*this)>::type, SELF >::value, "self wrong type" ); }
#define SELF(T) typedef T self; SELF_CHECK(T)

struct Foo {
  SELF(Foo); // works, self is defined as `Foo`
};
struct Bar {
  SELF(Foo); // fails
};

यह टेम्पलेट प्रकारों पर काम नहीं करता है, जैसा self_checkकि नहीं कहा जाता है, इसलिए इसका static_assertमूल्यांकन नहीं किया जाता है।

हम इसे हैक करने के लिए कुछ हैक भी कर सकते हैं template, साथ ही यह काम करने के लिए भी है, लेकिन इसकी समय पर लागत कम है।

#define TESTER_HELPER_TYPE \
template<typename T, std::size_t line> \
struct line_tester_t { \
  line_tester_t() { \
    static_assert( std::is_same< decltype(T::line_tester), line_tester_t<T,line> >::value, "test failed" ); \
    static_assert( std::is_same< decltype(&T::static_test_zzz), T*(*)() >::value, "test 2 failed" ); \
  } \
}

#define SELF_CHECK( SELF ) void self_check() { static_assert( std::is_same< typename std::decay<decltype(*this)>::type, SELF >::value, "self wrong type" ); }

#define SELF(T) typedef T self; SELF_CHECK(T); static T* static_test_zzz() { return nullptr; }; TESTER_HELPER_TYPE; line_tester_t<T,__LINE__> line_tester

structआपकी कक्षा में 1 बाइट का एक खाली आकार बनाया गया है। यदि आपका प्रकार तात्कालिक है, के selfखिलाफ परीक्षण किया जाता है।


यह भी बुरा नहीं है!
लाइट

templateवर्ग समर्थन विकल्पों के साथ अब @LightnessRacesinOrbit ।
यक्क - एडम नेवरामोंट

मैं इस बारे में बिल्कुल सोच रहा था क्योंकि मैं कल काम छोड़ रहा था। आपने मुझे इसमें हरा दिया :)। मैं इनलाइन लिंकिंग मुद्दों (एक ही प्रतीक Foo :: self_check () कई ऑब्जेक्ट फ़ाइलों में पाया) से बचने के लिए इनलाइन के रूप में self_check () घोषित करने का सुझाव दूंगा।
सूअर

1
@theswine: 9.3 / 2 C ++ मानक में एक पैराग्राफ का सूचकांक है, जो इस बात की गारंटी देता है कि वर्ग परिभाषा के शरीर में परिभाषित कक्षा सदस्य फ़ंक्शन पहले से ही, अंतर्निहित हैं inline। इसका मतलब है कि आपको लिखने की ज़रूरत नहीं है inline। इसलिए यदि आप inlineअपने पूरे करियर के लिए ऐसे हर क्लास मेंबर फंक्शन की परिभाषा के सामने लिख रहे हैं, तो आप अभी रुक सकते हैं;)
ऑर्बिट में लाइटनेस रेस

2
@LightnessRacesinOrbit ओह, वास्तव में मैं था। धन्यवाद, जो मुझे भविष्य में कुछ टाइपिंग से बचाएगा :)। मैं हमेशा से हैरान हूँ कि मैं C ++ के बारे में कितना नहीं जानता।
सूअर

11

मुझे यह भी लगता है कि यह असंभव है, यहाँ एक और असफल लेकिन IMHO दिलचस्प प्रयास है जो this-access से बचा जाता है :

template<typename T>
struct class_t;

template<typename T, typename R>
struct class_t< R (T::*)() > { using type = T; };

struct Foo
{
   void self_f(); using self = typename class_t<decltype(&self_f)>::type;
};

#include <type_traits>

int main()
{
    static_assert( std::is_same< Foo::self, Foo >::value, "" );
}

जो विफल रहता है क्योंकि C ++ के लिए आपको self_fकक्षा के साथ अर्हता प्राप्त करने की आवश्यकता होती है जब आप इसका पता लेना चाहते हैं :(


और एक ही समस्या एक नियमित int T::*पॉइंटर से सदस्य चर के साथ होती है। और int self_var; typedef decltype(&self_var) self_ptrया तो काम नहीं करता है, यह सिर्फ एक नियमित है int*
एमएसलटर्स

9

मुझे हाल ही में पता चला है कि ब्रेस-ऑर-इक्वलाइज़र*this में इसकी अनुमति है । (§ 5.1.1 में वर्णित से n3337 काम कर मसौदा ):

3 [..] अन्य संदर्भों में ऑब्जेक्ट एक्सप्रेशन के विपरीत, *thisसदस्य फ़ंक्शन बॉडी के बाहर क्लास मेंबर एक्सेस (5.2.5) के उद्देश्यों के लिए पूर्ण प्रकार का होना आवश्यक नहीं है। [..]

4 अन्यथा, यदि कोई सदस्य-this घोषणाकर्ता गैर-स्थैतिक डेटा सदस्य (9.2) को दसवीं कक्षा का घोषित करता है, तो अभिव्यक्ति वैकल्पिक ब्रेस-या- इक्वलाइज़र के भीतर टाइप "पॉइंटर टू एक्स" का प्रचलन है । यह सदस्य-घोषणाकर्ता में कहीं और नहीं दिखाई देगा ।

5 अभिव्यक्ति thisकिसी अन्य संदर्भ में प्रकट नहीं होगी। [ उदाहरण:

class Outer {
    int a[sizeof(*this)];               // error: not inside a member function
    unsigned int sz = sizeof(*this);    // OK: in brace-or-equal-initializer

    void f() {
        int b[sizeof(*this)];           // OK
        struct Inner {
            int c[sizeof(*this)];       // error: not inside a member function of Inner
        };
    }
};

- अंतिम उदाहरण ]

इसे ध्यान में रखते हुए, निम्न कोड:

struct Foo
{
    Foo* test = this;
    using self = decltype(test);

    static void smf()
    {
        self foo;
    }
};

#include <iostream>
#include <type_traits>

int main()
{
    static_assert( std::is_same< Foo::self, Foo* >::value, "" );
}

गुजरता डैनियल फ्रे के static_assert

Live example


आपके पास एक कष्टप्रद बेकार चर testहै
MM

@ मैट सच, लेकिन मुझे अभी भी यह दिलचस्प लगा।

1
यह बिना = this, सही काम कर सकता था ? और क्यों नहींusing self = Foo*;
user362515

1
हम निश्चित रूप से यहाँ कुछ भी हासिल नहीं किया, क्योंकि हमें testटाइप, उम, की घोषणा करनी थी Foo *!
पॉल सैंडर्स

4

जब तक प्रकार को संलग्न करने वाले वर्ग का सदस्य प्रकार होने की आवश्यकता नहीं है, selfजिसके साथ आप उपयोग कर सकते हैं decltype(*this)। यदि आप इसे अपने कोड में कई स्थानों पर उपयोग करते हैं, तो आप एक मैक्रो SELFको निम्नानुसार परिभाषित कर सकते हैं :

#define SELF decltype(*this)

2
और आप वर्ग के बाहर या नेस्टेड कक्षाओं में उपयोग नहीं कर सकते
ड्रेक्स

1
@ ड्रेक्स: यह कक्षा के बाहर उपलब्ध होने वाला नहीं है।
बेन वोइगट

@BenVoigt लेकिन इसे नेस्टेड क्लासेस में उपलब्ध होने के लिए बनाया गया है जो IMO का सबसे दिलचस्प उपयोग मामला है।
ड्रेक्स

1
मुझे ऐसा नहीं लगता। selfतत्काल वर्ग को नहीं घेरना चाहिए और न ही बाहरी वर्ग को? लेकिन मैं नहीं जानता कि php अच्छी तरह से है।
बेन वोइगट

1
@LightnessRacesinOrbit: मुझे लगता है कि कोड और त्रुटि "PHP के पास नेस्टेड प्रकार नहीं है" कहा जाता है?
बेन वोइगट

1

मेरा संस्करण प्रदान करें। सबसे अच्छी बात यह है कि इसका उपयोग मूल वर्ग के समान ही है। हालाँकि, यह टेम्पलेट कक्षाओं के लिए काम नहीं करता है।

template<class T> class Self;

#define CLASS(Name) \
class Name##_; \
typedef Self<Name##_> Name; \
template<> class Self<Name##_>

CLASS(A)
{
    int i;
    Self* clone() const { return new Self(*this); }
};

CLASS(B) : public A
{
    float f;
    Self* clone() const { return new Self(*this); }
};

1

Hvd द्वारा उत्तर देने पर, मैंने पाया कि केवल एक चीज जो गायब थी, वह संदर्भ को हटा रही थी, यही कारण है कि std :: is_same जाँच विफल (b / c परिणामी प्रकार वास्तव में प्रकार का एक संदर्भ है)। अब यह पैरामीटर-कम मैक्रो सभी काम कर सकता है। नीचे काम करने का उदाहरण (मैं जीसीसी 8.1.1 का उपयोग करता हूं)।

#define DEFINE_SELF \
    typedef auto _self_fn() -> std::remove_reference<decltype(*this)>::type; \
    using self = decltype(((_self_fn*)0)())

class A {
    public:
    DEFINE_SELF;
};

int main()
{
    if (std::is_same_v<A::self, A>)
        std::cout << "is A";
}

यह GCC की तुलना में अन्य संकलक पर संकलन नहीं करता है।
जेडू z ’

0

मैं "इसे स्वयं करने के लिए" का स्पष्ट समाधान दोहराऊंगा। यह कोड का succinct C ++ 11 संस्करण है, जो सरल कक्षाओं और वर्ग टेम्पलेट्स दोनों के साथ काम करता है:

#define DECLARE_SELF(Type) \
    typedef Type TySelf; /**< @brief type of this class */ \
    /** checks the consistency of TySelf type (calling it has no effect) */ \
    void self_check() \
    { \
        static_assert(std::is_same<decltype(*((TySelf*)(0))), \
            decltype(*this)>::value, "TySelf is not what it should be"); \
    } \
    enum { static_self_check_token = __LINE__ }; \
    static_assert(int(static_self_check_token) == \
        int(TySelf::static_self_check_token), \
        "TySelf is not what it should be")

आप इसे विचारधारा पर कार्रवाई में देख सकते हैं । उत्पत्ति, इस परिणाम के लिए अग्रणी नीचे है:

#define DECLARE_SELF(Type) typedef Type _TySelf; /**< @brief type of this class */

struct XYZ {
    DECLARE_SELF(XYZ)
};

यह एक अलग वर्ग के लिए कोड को कॉपी-पेस्ट करने और XYZ को बदलने की भूल के साथ स्पष्ट समस्या है, जैसे:

struct ABC {
    DECLARE_SELF(XYZ) // !!
};

मेरा पहला दृष्टिकोण बहुत मौलिक नहीं था - एक समारोह बनाना, जैसे:

/**
 *  @brief namespace for checking the _TySelf type consistency
 */
namespace __self {

/**
 *  @brief compile-time assertion (_TySelf must be declared the same as the type of class)
 *
 *  @tparam _TySelf is reported self type
 *  @tparam _TyDecltypeThis is type of <tt>*this</tt>
 */
template <class _TySelf, class _TyDecltypeThis>
class CSELF_TYPE_MUST_BE_THE_SAME_AS_CLASS_TYPE;

/**
 *  @brief compile-time assertion (specialization for assertion passing)
 *  @tparam _TySelf is reported self type (same as type of <tt>*this</tt>)
 */
template <class _TySelf>
class CSELF_TYPE_MUST_BE_THE_SAME_AS_CLASS_TYPE<_TySelf, _TySelf> {};

/**
 *  @brief static assertion helper type
 *  @tparam n_size is size of object being used as assertion message
 *      (if it's a incomplete type, compiler will display object name in error output)
 */
template <const size_t n_size>
class CStaticAssert {};

/**
 *  @brief helper function for self-check, this is used to derive type of this
 *      in absence of <tt>decltype()</tt> in older versions of C++
 *
 *  @tparam _TyA is reported self type
 *  @tparam _TyB is type of <tt>*this</tt>
 */
template <class _TyA, class _TyB>
inline void __self_check_helper(_TyB *UNUSED(p_this))
{
    typedef CStaticAssert<sizeof(CSELF_TYPE_MUST_BE_THE_SAME_AS_CLASS_TYPE<_TyA, _TyB>)> _TyAssert;
    // make sure that the type reported as self and type of *this is the same
}

/**
 *  @def __SELF_CHECK
 *  @brief declares the body of __self_check() function
 */
#define __SELF_CHECK \
    /** checks the consistency of _TySelf type (calling it has no effect) */ \
    inline void __self_check() \
    { \
        __self::__self_check_helper<_TySelf>(this); \
    }

/**
 *  @def DECLARE_SELF
 *  @brief declares _TySelf type and adds code to make sure that it is indeed a correct one
 *  @param[in] Type is type of the enclosing class
 */
#define DECLARE_SELF(Type) \
    typedef Type _TySelf; /**< @brief type of this class */ \
    __SELF_CHECK

} // ~self

यह एक तरह से लंबा है, लेकिन कृपया यहां मेरे साथ रहें। इसके बिना C ++ 03 में काम करने का लाभ होता है decltype, क्योंकि __self_check_helperफ़ंक्शन को प्रकार में कटौती करने के लिए नियोजित किया जाता है this। इसके अलावा, कोई भी नहीं है static_assert, लेकिन sizeof()ट्रिक इसके बजाय कार्यरत है। आप इसे C ++ 0x के लिए बहुत छोटा बना सकते हैं। अब यह टेम्प्लेट के लिए काम नहीं करेगा। इसके अलावा, मैक्रो के साथ एक छोटी सी समस्या है जो अंत में अर्धविराम की उम्मीद नहीं करता है, यदि पांडित्य के साथ संकलन किया जाता है, तो यह एक अतिरिक्त अनावश्यक अर्धविराम के बारे में शिकायत करेगा (या आप के शरीर में अर्धविराम में समाप्त नहीं होने वाले विषम दिखने वाले मैक्रो के साथ छोड़ दिया जाएगा XYZऔर ABC)।

Typeजो पास है उस पर एक चेक बनाना एक DECLARE_SELFविकल्प नहीं है, क्योंकि यह केवल XYZकक्षा की जांच करेगा (जो ठीक है), से अनजान हैABC जिसके (जिसमें त्रुटि है)। और उसके बाद उसने मुझे मारा। एक अतिरिक्त भंडारण शून्य लागत समाधान जो टेम्पलेट्स के साथ काम करता है:

namespace __self {

/**
 *  @brief compile-time assertion (_TySelf must be declared the same as the type of class)
 *  @tparam b_check is the asserted value
 */
template <bool b_check>
class CSELF_TYPE_MUST_BE_THE_SAME_AS_CLASS_TYPE2;

/**
 *  @brief compile-time assertion (specialization for assertion passing)
 */
template <>
class CSELF_TYPE_MUST_BE_THE_SAME_AS_CLASS_TYPE2<true> {};

/**
 *  @def DECLARE_SELF
 *  @brief declares _TySelf type and adds code to make sure that it is indeed a correct one
 *  @param[in] Type is type of the enclosing class
 */
#define DECLARE_SELF(Type) \
    typedef Type _TySelf; /**< @brief type of this class */ \
    __SELF_CHECK \
    enum { __static_self_check_token = __LINE__ }; \
    typedef __self::CStaticAssert<sizeof(CSELF_TYPE_MUST_BE_THE_SAME_AS_CLASS_TYPE2<int(__static_self_check_token) == int(_TySelf::__static_self_check_token)>)> __static_self_check

} // ~__self 

यह केवल एक अद्वितीय एनम वैल्यू (या कम से कम यूनिक है कि आप अपने सभी कोड एक लाइन पर नहीं लिखते हैं) पर स्थैतिक अभिकथन करता है, किसी भी प्रकार की तुलना वाली प्रवंचना को नियोजित नहीं किया जाता है, और यह स्थिर असेंबल के रूप में काम करता है, यहां तक ​​कि टेम्प्लेट में भी । और बोनस के रूप में - अंतिम अर्धविराम अब आवश्यक है :)।

मुझे एक अच्छी प्रेरणा देने के लिए मैं यक्क को धन्यवाद देना चाहता हूं। मैं उसका जवाब देखे बिना यह नहीं लिखूंगा।

वीएस 2008 और जी ++ 4.6.3 के साथ परीक्षण किया गया। वास्तव में, XYZऔर ABCउदाहरण के साथ, यह शिकायत करता है:

ipolok@ivs:~$ g++ self.cpp -c -o self.o
self.cpp:91:5: error: invalid application of âsizeofâ to incomplete type â__self::CSELF_TYPE_MUST_BE_THE_SAME_AS_CLASS_TYPE2<false>â
self.cpp:91:5: error: template argument 1 is invalid
self.cpp: In function âvoid __self::__self_check_helper(_TyB*) [with _TyA = XYZ, _TyB = ABC]â:
self.cpp:91:5:   instantiated from here
self.cpp:58:87: error: invalid application of âsizeofâ to incomplete type â__self::CSELF_TYPE_MUST_BE_THE_SAME_AS_CLASS_TYPE<XYZ, ABC

अब अगर हम ABC को एक टेम्प्लेट बनाते हैं:

template <class X>
struct ABC {
    DECLARE_SELF(XYZ); // line 92
};

int main(int argc, char **argv)
{
    ABC<int> abc;
    return 0;
}

हमें मिल जाएगा:

ipolok@ivs:~$ g++ self.cpp -c -o self.o
self.cpp: In instantiation of âABC<int>â:
self.cpp:97:18:   instantiated from here
self.cpp:92:9: error: invalid application of âsizeofâ to incomplete type â__self::CSELF_TYPE_MUST_BE_THE_SAME_AS_CLASS_TYPE2<false>â

केवल लाइन-नंबर चेक ट्रिगर हुआ, क्योंकि फ़ंक्शन चेक संकलित नहीं किया गया था (जैसा कि अपेक्षित था)।

C ++ 0x (और बिना अंडरस्कोर के) के साथ, आपको बस आवश्यकता होगी:

namespace self_util {

/**
 *  @brief compile-time assertion (tokens in class and TySelf must match)
 *  @tparam b_check is the asserted value
 */
template <bool b_check>
class SELF_TYPE_MUST_BE_THE_SAME_AS_CLASS_TYPE;

/**
 *  @brief compile-time assertion (specialization for assertion passing)
 */
template <>
class SELF_TYPE_MUST_BE_THE_SAME_AS_CLASS_TYPE<true> {};

/**
 *  @brief static assertion helper type
 *  @tparam n_size is size of object being used as assertion message
 *      (if it's a incomplete type, compiler will display object name in error output)
 */
template <const size_t n_size>
class CStaticAssert {};

#define SELF_CHECK \
    /** checks the consistency of TySelf type (calling it has no effect) */ \
    void self_check() \
    { \
        static_assert(std::is_same<TySelf, decltype(*this)>::value, "TySelf is not what it should be"); \
    }

#define DECLARE_SELF(Type) \
    typedef Type TySelf; /**< @brief type of this class */ \
    SELF_CHECK \
    enum { static_self_check_token = __LINE__ }; \
    typedef self_util::CStaticAssert<sizeof(SELF_TYPE_MUST_BE_THE_SAME_AS_CLASS_TYPE<int(static_self_check_token) == int(TySelf::static_self_check_token)>)> static_self_check

} // ~self_util

मेरा मानना ​​है कि CStaticAssert बिट को अभी भी आवश्यक रूप से आवश्यक है क्योंकि यह एक प्रकार का उत्पादन करता है, जो टेम्पलेट बॉडी में टाइप-एफ-एड है (मुझे लगता है कि ऐसा नहीं किया जा सकता है static_assert)। इस दृष्टिकोण का लाभ अभी भी इसकी शून्य लागत है।


आप अनिवार्य रूप से static_assertयहां फिर से लागू हो रहे हैं, क्या आप नहीं हैं? इसके अलावा, आपका पूरा कोड अमान्य है क्योंकि आप अवैध (आरक्षित) पहचानकर्ताओं का उपयोग कर रहे हैं।
कोनराड रुडोल्फ

@KonradRudolph हाँ, यह वास्तव में मामला है। मेरे पास उस समय C ++ 0x नहीं है, इसलिए मैंने जवाब देने के लिए static_assert को फिर से लागू किया। मैं कहता हूं कि जवाब में। क्या यह अमान्य है? क्या आप बता सकते हैं कि कैसे? यह ठीक संकलित है, मैं अभी इसका उपयोग कर रहा हूं।
सूअर

1
पहचानकर्ता अमान्य हैं, क्योंकि C ++ एक प्रमुख अंडरस्कोर के साथ सब कुछ सुरक्षित रखता है, इसके बाद अपरकेस अक्षर, साथ ही वैश्विक गुंजाइश में दो प्रमुख अंडरस्कोर संकलक के लिए होता है। उपयोगकर्ता कोड का उपयोग नहीं करना चाहिए, लेकिन सभी संकलक इसे त्रुटि के रूप में चिह्नित करेंगे।
कोनराड रुडोल्फ

@KonradRudolph मैं देख रहा हूं, मुझे यह नहीं पता था। मेरे पास बहुत सारे कोड हैं जो इसका उपयोग करते हैं, कभी भी न तो लिनक्स / मैक / विंडोज पर इसके साथ समस्या थी। लेकिन मुझे लगता है कि यह जानना अच्छा है।
सूअर

0

मैं इन निराला टेम्पलेट्स के बारे में सब कुछ नहीं जानता, कैसे के बारे में कुछ सुपर सरल:

#define DECLARE_TYPEOF_THIS typedef CLASSNAME typeof_this
#define ANNOTATED_CLASSNAME(DUMMY) CLASSNAME

#define CLASSNAME X
class ANNOTATED_CLASSNAME (X)
{
public:
    DECLARE_TYPEOF_THIS;
    CLASSNAME () { moi = this; }
    ~CLASSNAME () { }
    typeof_this *moi;
    // ...
};    
#undef CLASSNAME

#define CLASSNAME Y
class ANNOTATED_CLASSNAME (Y)
{
    // ...
};
#undef CLASSNAME

नौकरी तब तक की जाती है, जब तक कि आप मैक्रों के एक जोड़े को खड़ा नहीं कर सकते। आप CLASSNAMEअपने कंस्ट्रक्टर (ओं) को घोषित करने के लिए भी इस्तेमाल कर सकते हैं (और, बेशक, विध्वंसक)।

लाइव डेमो


1
यह कैसे वर्ग / तो इस्तेमाल किया जाना चाहिए कर सकते हैं पर काफी स्पष्ट प्रभाव है
कक्षा में लपट दौड़

@LightnessRacesinOrbit ऐसा कैसे? मैं इसे नहीं देखता। मैंने किया, प्रतिबिंब पर, अपने मूल पद के अंतिम वाक्य को हटा दें। मूल रूप से मेरे पास जो था वह आपको यह सोचने के लिए प्रेरित कर सकता है।
पॉल सैंडर्स
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.