C ++ 'da özerk bir "self" üye türü uygulayabilir miyim?


101

C ++ , çevreleyen sınıfın türünü değerlendiren PHP'nin anahtar sözcüğü eşdeğerinden yoksundur .self

Sınıf başına sahte yapmak yeterince kolaydır:

struct Foo
{
   typedef Foo self;
};

ama Footekrar yazmak zorunda kaldım . Belki bir gün bunu yanlış anlarım ve sessiz bir böceğe neden olurum.

decltypeBu işi "özerk" bir şekilde yapmak için bazı ve arkadaşları kombinasyonu kullanabilir miyim ? Aşağıdakileri zaten denedim ama thiso yerde geçerli değil:

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

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

( staticAynı şeyi yapan eşdeğeri hakkında endişelenmeyeceğim , ancak geç bağlama ile.)


9
this_tmuhtemelen normal C ++ adlandırma ile daha uyumlu olacaktır.
Bartek Banachewicz

3
@BartekBanachewicz: or this_type
PlasmaHH

10
@Praetorian, bunun bir teklif olup olmadığını hatırlayamıyorum, ama biri önerdi auto()ve ~auto()ctors / dtors için. En azını söylemek ilginç. Bu amaç için kullanılırsa belki typedef auto self;, ama bu bana biraz yarım yamalak geliyor.
chris

11
Dürüst olmak gerekirse, bunu mümkün kılmak için sözdizimi önerecek olsaydım, muhtemelen decltype(class), belki bir decltype(struct)eşdeğeriyle olurdu . Bu auto, belirli bir bağlamdan çok daha net ve bununla ilgili dile dayalı herhangi bir sorun görmüyorum decltype(auto).
chris

11
Hataları önlemek istediğiniz için, static_assert ile, void _check() { static_assert(std::is_same<self&, decltype(*this)>::value, "Correct your self type"); }sınıf şablonlarıyla çalışmıyor gibi bir kukla üye işlevi kurabilirsiniz , ancak ...
milleniumbug

Yanıtlar:


39

İşte Foo türünü tekrar etmeden bunu nasıl yapabileceğiniz:

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;
    }
};

Bundan türetmek Fooistiyorsanız, makroyu WITH_SELF_DERIVEDaşağıdaki şekilde kullanmalısınız:

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

İstediğiniz kadar temel sınıfla birden fazla kalıtım bile yapabilirsiniz (çeşitli şablonlar ve değişken makrolar sayesinde):

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

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

Bunu gcc 4.8 ve clang 3.4 üzerinde çalışmak için doğruladım.


18
Sanırım cevap "hayır, ama Ralph yapabilir!" ;)
Orbit'te Hafiflik Yarışları

3
Bu, typedef'i oraya koymaktan nasıl daha üstündür? Ve tanrım, neden yazı tipine ihtiyacın olsun ki? Neden?
Miles Rout

7
@MilesRout Bu soru ile ilgili bir soru, cevabı değil. Yazılım geliştirmede (ve özellikle bakımda) çoğu durumda, kodda fazlalıklardan kaçınmak yararlıdır, böylece bir yerde bir şeyi değiştirmek, başka bir yerde kodu değiştirmenizi gerektirmez. Bütün mesele bu autove decltypeya da bu durumda self.
Ralph Tandetzky

1
template<typename T>class Self{protected: typedef T self;}; class WITH_SELF(Foo) : public Bar, private Baz {};daha basit olurdu ve kalıtım üzerinde daha kesin bir kontrole izin verirdi - buna karşı herhangi bir sebep var mı?
Aconcagua

@mmmmmmmm, "Kendini Tekrar Etme" ilkesini derinden takdir etmeyi öğrenmediyseniz, şansınız henüz yeterince / ciddi şekilde kodlamamışsınızdır. Bu "dağınıklık" (aslında ondan çok uzak), zarif olmayan bir dil özelliğinden (veya yanlış anlamadan, hatta bazı katı önlemlerle eksiklikten) bahsetme bağlamında oldukça zarif bir düzeltmedir.
Sz.

38

Olası bir çözüm (türü yine de bir kez yazmanız gerektiğinden):

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

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

Daha güvenli bir sürüm için bunun Taslında aşağıdakilerden türediğini garanti edebiliriz Self<T>:

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

Aktarılan static_asserttürlerin std::is_base_oftamamlanması gerektiğinden , bir üye işlevinin muhtemelen kontrol etmenin tek yolu olduğuna dikkat edin .


4
typenameYazıya gerek yok . Ve bu, fazlalıkların sayısını azaltmadığından, bunun uygulanabilir bir alternatif olduğunu düşünmüyorum.
Konrad Rudolph

Tam olarak aynı Fooadı tekrarlama problemi var .
Bartek Banachewicz

6
Bu ise tekrarlama birbirine çok yakın olduğu için, olsa da, marjinal iyi orijinal yaklaşımından daha. Soru için bir çözüm değil, ancak en iyi durum geçici çözümünde değerli bir girişim için +1.
Orbit'te Hafiflik Yarışları

4
Bu çözümü birkaç kez kullandım ve bir KÖTÜ şeyi var: daha sonra türetirken Foo, ya (1) T'yi yukarı doğru yapraktan gelene doğru yaymanız ya da (2) SelfT'den birçok kez miras almayı hatırlamanız gerekir. veya (3) tüm çocukların Üs olduğunu kabul edin .. kullanılabilir, ama hoş.
quetzalcoatl

@quetzalcoatl: Kopyalamak selfyerine kopyalamaya çalıştığım için staticsorun değil.
Orbit'te Hafiflik Yarışları

33

Bunu sizin için yapacak normal sınıf bildirimi yerine bir makro kullanabilirsiniz.

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

Ve sonra beğen

CLASS_WITH_SELF(Foo) 
};

#define END_CLASS }; muhtemelen okunabilirliğe yardımcı olacaktır.


Ayrıca @ Paranaix'leri alıp Selfkullanabilirsiniz (gerçekten hacklenmeye başlar)

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

class WITH_SELF(Foo) {
};

18
EWWWW END_CLASS. Bu tamamen gereksiz.
Puppy

31
@DeadMG Bazı insanların daha fazla tutarlılığı sevebileceğini düşünüyorum; Sonuçta, ilk makro kullanımı bitmiyor {, bu nedenle }metin editörlerinin de muhtemelen hoşlanmayacağı "asılı" dır.
Bartek Banachewicz

6
Güzel fikir, ama temelde makrolara karşı olmasam da, burada kullanımını yalnızca C ++ kapsamını taklit etseydi, yani kullanılabilir olsaydı CLASS_WITH_SELF(foo) { … };- ve bence bunu başarmanın imkansız olduğunu düşünüyorum.
Konrad Rudolph

2
@KonradRudolph Bunu yapmanın bir yolunu da ekledim. Sevdiğimden değil, sadece eksiksizlik uğruna
Bartek Banachewicz

1
Yine de bu yaklaşımla ilgili bazı sorunlar var. İlki (başka bir makro parametre (ler) kullanmadığınız sürece) kolayca sınıf devralma yapmak için izin verir ve ikinci tüm sorunları var değil devralan bunun Selfsahiptir.
Bartek Banachewicz

31

Olumlu bir kanıtım yok ama bunun imkansız olduğunu düşünüyorum . Aşağıdakiler - sizin girişiminizle aynı nedenden dolayı - başarısız olur ve bence alabileceğimiz en uzağı budur:

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

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

Esasen, bu ne gösteriyor bizim typedef bildirmek istiyorum hangi kapsam basitçe olmasıdır hiçbir erişim için (o doğrudan veya dolaylı) thisve sınıf türü veya isme almanın başka (derleyici bağımsız) bir yolu var.


4
Bu muhtemelen C ++ 1y'nin dönüş türü kesintisi ile olacak mı?
2014

4
@dyp Cevabımın amacı için hiçbir şeyi değiştirmeyecek. Buradaki hata, sondaki dönüş türünde değil, çağrıdadır.
Konrad Rudolph

1
@quetzalcoatl: İçgüdüleri decltypedeğerlendirilmemiş bir bağlam, bu nedenle üye işlevini çağırmak sorun değil (denenmeyecek)
Orbit'te Hafiflik Yarışları

1
@TomKnapen Clang ile deneyin, başarısız olur. Bildiğim kadarıyla GCC tarafından kabul edilmiş olması bir hata.

4
FWIW, statik olmayan bir veri üyesi struct S { int i; typedef decltype(i) Int; };olmasına rağmen çalışır i. Çalışır çünkü decltypebasit bir adın ifade olarak değerlendirilmediği özel bir istisnası vardır. Ancak bu olasılığı soruyu yanıtlayacak şekilde kullanmanın bir yolunu düşünemiyorum.

21

Hem GCC hem de clang'da işe yarayan şey, typedef işlevinin sondaki dönüş türünü thiskullanarak başvuran bir typedef oluşturmaktır this. Bu, statik bir üye işlevin bildirimi olmadığından, kullanımı thistolere edilir. Daha sonra bu typedef'i tanımlamak için kullanabilirsiniz self.

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

struct Foo {
    DEFINE_SELF();
};

struct Bar {
    DEFINE_SELF();
};

Ne yazık ki, standardın katı bir şekilde okunması bunun bile geçerli olmadığını söylüyor. Clang'ın yaptığı şey this, statik üye işlevinin tanımında kullanılmayan denetlemektir . Ve burada gerçekten değil. GCC, thisişlevin türüne bakılmaksızın bir sondaki dönüş türünde kullanılırsa aldırmaz , staticüye işlevler için bile izin verir . Bununla birlikte, standardın gerçekte gerektirdiği şey, thisstatik olmayan bir üye işlevinin (veya statik olmayan veri üyesi başlatıcısının) tanımının dışında kullanılmamasıdır. Intel bunu doğru anlar ve bunu reddeder.

Verilen:

  • this yalnızca statik olmayan veri üyesi başlatıcılarında ve statik olmayan üye işlevlerinde izin verilir ([ifade.prim.general] p5),
  • statik olmayan veri üyeleri, türlerini başlatıcıdan çıkarılamaz ([dcl.spec.auto] p5),
  • statik olmayan üye işlevlere, bir işlev çağrısı bağlamında yalnızca nitelenmemiş bir adla başvurulabilir ([ifade.ref] p4)
  • statik olmayan üye işlevler, değerlendirilmemiş bağlamlarda bile yalnızca nitelenmemiş adla çağrılabilir this([over.call.func] p3),
  • Nitelikli ad veya üye erişimi ile statik olmayan üye işleve yapılan başvuru, tanımlanmakta olan türe bir başvuru gerektirir

Sanırım kesin olarak şunu söyleyebilirim ki, bir şekilde selftür adını bir yere dahil etmeden uygulamanın hiçbir yolu yoktur .

Düzenleme : Önceki muhakememde bir kusur var. "Statik olmayan üye işlevler, değerlendirilmemiş bağlamlarda bile, bu kullanılabildiğinde ([over.call.func] p3) yalnızca nitelenmemiş adla çağrılabilir," yanlıştır. Aslında ne diyor

Anahtar kelime this(9.3.2) kapsam içindeyse ve sınıfa Tveya türetilmiş bir sınıfına atıfta bulunuyorsa T, örtülü nesne argümanı olur (*this). Anahtar kelime thiskapsam içinde değilse veya başka bir sınıfa atıfta bulunuyorsa, o zaman uydurulmuş bir nesne türü Törtülü nesne argümanı haline gelir. Argüman listesi, uydurulmuş bir nesne tarafından artırılırsa ve aşırı yük çözümü, statik olmayan üye işlevlerinden birini seçerse T, çağrı biçimsizdir.

Statik üye işlevinin içinde thisgörünmeyebilir, ancak yine de mevcuttur.

Ancak yorumların başına, statik üye işlevi içinde, dönüşümü f()için (*this).f()yapılması olmaz ve bu yerine getirilmemesi halinde, ardından [expr.call] p1 ihlal edildi:

[...] Bir üye işlev çağrısı için, sonek ifadesi örtük (9.3.1, 9.4) veya açık bir sınıf üyesi erişimi (5.2.5) olmalıdır ve bunların [...]

üye erişimi olmayacağı için. Yani bu bile işe yaramaz.


Sanırım [class.mfct.non-static] / 3 bunun _self_fn_1()"dönüştürülmüş" olduğunu söylüyor (*this)._self_fn_1(). Yine de bunun yasadışı olup olmadığından emin değilim.
2014

@dyp "Kullanılabileceği Xbir bağlamda sınıfın bir üyesinde thiskullanılıyor" diyor, bu yüzden dönüşümün yapıldığını düşünmüyorum.

1
Ama o zaman ne örtük ne de açık bir sınıf üyesi erişimi değil ..? [expr.call] / 1 "Bir üye işlev çağrısı için, postfix ifadesi örtük veya açık bir sınıf üyesi erişimi olacaktır [...]"
dyp

(Demek istediğim, sahip olduğunuzda ne olur auto _self_fn_1() -> decltype(*this); auto _self_fn_1() const -> decltype(*this);?)
dyp

@dyp [ifade.cagri] / 1 iyi bir nokta, daha yakından bakmam gerekecek. Hakkında constaşırı olsa: bu bir sorun değil. 5.1p3, statik üye işlevlerine de uygulanacak şekilde özel olarak değiştirilmiştir ve türünün / (olmadan ) thisolduğunu söyler , çünkü bildiriminde yoktur . 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
};

bu, self_checkçağrılmadığı gibi şablon türlerinde çalışmaz , bu nedenle static_assertdeğerlendirilmez.

Bunun için de çalışmasını sağlamak için bazı kesmeler yapabiliriz template, ancak küçük bir çalışma süresi maliyeti vardır.

#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

structsınıfınızda 1 baytlık boş bir boyut oluşturulur. Türünüz örneklenirse, selfkarşı test edilir.


Bu da fena değil!
Orbit'te Hafiflik Yarışları

@LightnessRacesinOrbit artık templatesınıf destek seçenekleriyle.
Yakk - Adam Nevraumont

Dün işten ayrılırken tam olarak bunu düşünüyordum. Beni yendin :). Bağlantı sorunlarından kaçınmak için self_check () 'i satır içi olarak bildirmenizi öneririm (birden fazla nesne dosyasında bulunan aynı sembol Foo :: self_check ()).
domuz

1
@theswine: 9.3 / 2, sınıf tanımının gövdesinde tanımlanan sınıf üyesi işlevlerinin halihazırda örtük olarak olduğunu garanti eden C ++ standardındaki bir paragrafın dizinidir inline. Bu, hiç yazmanıza gerek olmadığı anlamına gelir inline. Öyleyse inline, tüm kariyeriniz için böyle bir sınıf üyesi işlev tanımının önünde yazıyorsanız , şimdi bırakabilirsiniz;)
Orbit'te Hafiflik Yarışları

2
@LightnessRacesinOrbit Oh, aslında öyleydim. Teşekkür ederim, bu bana gelecekte yazı yazmaktan tasarruf edecek :). C ++ hakkında ne kadar bilmediğime hep şaşırmışımdır.
domuz

11

Ayrıca bunun imkansız olduğunu düşünüyorum, işte başka bir başarısız ama IMHO- thiserişimini engelleyen ilginç bir girişim :

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, "" );
}

bu başarısız olur çünkü C ++, self_fadresini almak istediğinizde sınıfta kalifiye olmanızı gerektirir :(


Ve aynı sorun int T::*üye değişkene düzenli bir gösterici ile olur . Ve int self_var; typedef decltype(&self_var) self_ptrişe yaramıyor, bu sadece normal int*.
MSalters

9

Yakın zamanda buna *thisbir küme ayracı veya ekolayzerde izin verildiğini keşfettim . (§ 5.1.1 anlatıldığı gelen n3337 çalışma taslak ):

3 [..] Diğer bağlamlardaki nesne ifadesinin aksine *this, üye işlev gövdesi dışındaki sınıf üyesi erişimi (5.2.5) amaçları için tam tipte olması gerekli değildir. [..]

4 Aksi takdirde, bir üyethis bildirici X sınıfının statik olmayan bir veri üyesini (9.2) bildirirse, ifade , isteğe bağlı küme ayracı veya ekolayzer içinde "X'e işaretçi" türünün bir prdeğeridir . Üye beyanında başka bir yerde görünmez .

5 İfade thisbaşka bir bağlamda görünmeyecektir. [ Örnek:

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
        };
    }
};

- son örnek ]

Bunu akılda tutarak, aşağıdaki kod:

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, "" );
}

Daniel Frey's geçer static_assert.

Live example


testYine de can sıkıcı, işe yaramaz bir değişkeniniz var
MM

@Matt True, ama yine de ilginç buldum.

1
Bu olmadan da işe yarayabilirdi = this, değil mi? Ve neden sadeceusing self = Foo*;
user362515

1
Burada kesinlikle bir şey kazanmadık, çünkü testtürden biri olduğumuzu beyan etmemiz gerekiyordu , um Foo *,!
Paul Sanders

4

Türün, kapsayan sınıfın üye türü olması gerekmedikçe, kullanımını selfile değiştirebilirsiniz decltype(*this). Kodunuzun birçok yerinde kullanırsanız SELFaşağıdaki gibi bir makro tanımlayabilirsiniz :

#define SELF decltype(*this)

2
Ve bunu sınıfın dışında veya iç içe geçmiş sınıflarda kullanamazsınız
Drax

1
@Drax: Sınıfın dışında olmaması gerekiyor.
Ben Voigt

@BenVoigt Ancak, IMO'nun en ilginç kullanım durumu olan iç içe geçmiş sınıflarda mevcut olduğu varsayılmaktadır.
Drax

1
Ben öyle düşünmüyorum. Olmamalı selfdış sınıf derhal çevreleyen sınıfını ifade değil? Ama php'yi o kadar iyi bilmiyorum.
Ben Voigt

1
@LightnessRacesinOrbit: Kod ve hatanın "PHP'nin iç içe geçmiş türleri yok" demesi gerektiğini tahmin ediyorum?
Ben Voigt

1

Sürümümü sağlayın. En iyi şey, kullanımının yerel sınıfla aynı olmasıdır. Ancak, şablon sınıfları için çalışmaz.

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'nin cevabına dayanarak, eksik olan tek şeyin referansı kaldırmak olduğunu buldum, bu yüzden std :: is_same kontrolü başarısız oluyor (b / c ortaya çıkan tür aslında türe bir referanstır). Artık bu parametresiz makro tüm işi yapabilir. Aşağıdaki çalışma örneği (GCC 8.1.1 kullanıyorum).

#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 dışındaki diğer derleyicilerde derlenmez.
zedu

0

"Kendin yapmak zorunda olmanın" bariz çözümünü tekrar edeceğim. Bu, kodun hem basit sınıflarla hem de sınıf şablonlarıyla çalışan kısa ve öz C ++ 11 sürümüdür:

#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")

Bunu ideone'da eylem halinde görebilirsiniz . Bu sonuca götüren oluşum aşağıdadır:

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

struct XYZ {
    DECLARE_SELF(XYZ)
};

Bu, kodu farklı bir sınıfa kopyalayıp yapıştırmak ve XYZ'yi değiştirmeyi unutmak gibi bariz bir soruna sahiptir, örneğin:

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

İlk yaklaşımım çok orijinal değildi - şöyle bir işlev yapmak:

/**
 *  @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

Biraz uzun, ama lütfen burada benimle kalın. Bu, C ++ 03 olmadan çalışma avantajına sahiptir decltype, çünkü __self_check_helperişlevin türünü çıkarmak için kullanılır this. Ayrıca, yok static_assert, ama sizeof()onun yerine hile kullanılıyor. C ++ 0x için çok daha kısaltabilirsiniz. Şimdi bu şablonlar için çalışmayacak. Ayrıca, makronun sonunda noktalı virgül beklememesi ile ilgili küçük bir sorun var, eğer bilgiçlikle derleme yapılıyorsa, fazladan gereksiz bir noktalı virgülden şikayet edecek (veya XYZve ABC).

Bir kontrol yapma Typegeçirilir o DECLARE_SELFsadece kontrol gibi, bir seçenek değildir XYZkayıtsız sınıfını (ok olan), ABC(hata var olan). Ve sonra bana çarptı. Şablonlarla çalışan, ek depolama gerektirmeyen sıfır maliyetli bir çözüm:

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 

Bu, benzersiz bir enum değeri üzerinde statik iddia (veya en azından tüm kodunuzu tek bir satıra yazmamanız durumunda benzersiz) yapar, tür karşılaştırma hilesi kullanılmaz ve şablonlarda bile statik iddia olarak çalışır. . Ve bonus olarak - son noktalı virgül artık gereklidir :).

Bana iyi bir ilham verdiği için Yakk'e teşekkür etmek isterim. Bunu önce cevabını görmeden yazmazdım.

VS 2008 ve g ++ 4.6.3 ile test edilmiştir. Nitekim XYZve ABCörnekle şikayet ediyor:

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

Şimdi ABC'yi bir şablon yaparsak:

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

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

Alacağız:

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>â

İşlev kontrolü derlenmediğinden (beklendiği gibi) yalnızca satır numarası kontrolü tetiklendi.

C ++ 0x ile (ve kötü alt çizgiler olmadan), şunlara ihtiyacınız olacaktır:

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 bitinin, şablon gövdesinde yazılan bir tip ürettiği için maalesef hala gerekli olduğuna inanıyorum (sanırım aynısı ile yapılamaz static_assert). Bu yaklaşımın avantajı hala sıfır maliyetidir.


Esasen static_assertburada yeniden uyguluyorsunuz , değil mi? Ayrıca, yasa dışı (ayrılmış) tanımlayıcılar kullandığınız için kodunuzun tamamı geçersizdir.
Konrad Rudolph

@KonradRudolph Evet, gerçekten durum bu. Şu anda C ++ 0x'im yok, bu yüzden tam yanıt sağlamak için static_assert'i yeniden uyguladım. Bunu cevapta söylüyorum. Geçersiz mi? Nasıl olduğunu söyleyebilir misin? İyi derlendi, şu anda kullanıyorum.
domuz

1
Tanımlayıcılar geçersizdir çünkü C ++ derleyici için önde gelen alt çizgi ve ardından büyük harf ile her şeyi ve genel kapsamda iki önde gelen alt çizgiyi ayırır. Kullanıcı kodu onu kullanmamalıdır, ancak tüm derleyiciler bunu bir hata olarak işaretlemeyecektir.
Konrad Rudolph

@KonradRudolph Görüyorum, bunu bilmiyordum. Bunu kullanan çok sayıda kodum var, ne Linux / Mac / Windows'ta hiçbir zaman sorun yaşamadım. Ama sanırım bilmek güzel.
domuz

0

Bu tuhaf şablonların hepsini bilmiyorum, süper basit bir şeye ne dersiniz:

#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

Birkaç makroya dayanamadığınız sürece iş bitti. CLASSNAMEYapıcılarınızı (ve tabii ki yıkıcıyı) bildirmek için bile kullanabilirsiniz .

Canlı demo .


1
Sınıfın daha sonra nasıl kullanılacağı / kullanılması gerektiği konusunda oldukça belirgin bir etkisi var
Hafiflik Yarışları Orbit

@LightnessRacesinOrbit Nasıl yani? Ben görmüyorum Düşündükten sonra, orijinal gönderimin son cümlesini kaldırdım. Orada sahip olduğum şey, sizi bunu düşünmeye sevk etmiş olabilir.
Paul Sanders
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.