Dapatkah saya menerapkan jenis `self` member otonom di C ++?

101

C ++ tidak memiliki padanan dengan selfkata kunci PHP , yang mengevaluasi jenis kelas penutup.

Cukup mudah untuk memalsukannya berdasarkan kelas:

struct Foo
{
   typedef Foo self;
};

tapi saya harus menulis Foolagi. Mungkin saya akan mendapatkan kesalahan ini suatu hari dan menyebabkan bug diam.

Dapatkah saya menggunakan kombinasi dari decltypedan teman untuk membuat ini berfungsi "secara mandiri"? Saya sudah mencoba yang berikut ini tetapi thistidak valid di tempat itu:

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

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

(Saya tidak akan khawatir tentang padanannya static, yang melakukan hal yang sama tetapi dengan pengikatan yang terlambat.)

Balapan Ringan dalam Orbit
sumber
9
this_tmungkin lebih selaras dengan penamaan C ++ biasa.
Bartek Banachewicz
3
@BartekBanachewicz: atau tipe_ini
PlasmaHH
10
@Praetorian, saya tidak ingat apakah itu proposal atau bukan, tetapi seseorang menyarankan auto()dan ~auto()untuk pemberi pinjaman. Menarik untuk sedikitnya. Jika digunakan untuk tujuan itu, mungkin typedef auto self;, tapi bagi saya itu agak samar.
chris
11
Sejujurnya, jika saya akan menyarankan sintaks untuk memungkinkan hal ini, mungkin saja decltype(class), mungkin dengan decltype(struct)padanan. Itu jauh lebih jelas daripada hanya autodalam konteks tertentu dan saya tidak melihat ada masalah dengan itu sesuai dengan bahasa berdasarkan decltype(auto).
chris
11
Karena Anda ingin menghindari kesalahan, Anda dapat mengatur fungsi anggota dummy dengan static_assert, seperti void _check() { static_assert(std::is_same<self&, decltype(*this)>::value, "Correct your self type"); }Tidak bekerja dengan templat kelas, meskipun ...
milleniumbug

Jawaban:

39

Inilah cara Anda melakukannya tanpa mengulangi jenis Foo:

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

Jika Anda ingin menurunkannya Foomaka Anda harus menggunakan makro WITH_SELF_DERIVEDdengan cara berikut:

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

Anda bahkan dapat melakukan banyak pewarisan dengan kelas dasar sebanyak yang Anda inginkan (berkat template variadic dan makro variadic):

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

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

Saya telah memverifikasi ini untuk bekerja pada gcc 4.8 dan clang 3.4.

Ralph Tandetzky
sumber
18
Kurasa jawabannya "tidak, tapi Ralph bisa!" ;)
Lightness Races di Orbit
3
Bagaimana ini bisa lebih baik daripada hanya meletakkan typedef di sana? Dan Tuhan, mengapa Anda bahkan membutuhkan typedef? Mengapa?
Rute Miles
7
@MilesRout Ini adalah pertanyaan tentang pertanyaan, bukan jawabannya. Dalam banyak kasus dalam pengembangan perangkat lunak (dan terutama pemeliharaan), sangat membantu untuk menghindari redundansi dalam kode, sehingga mengubah sesuatu di satu tempat tidak mengharuskan Anda mengubah kode di tempat lain. Itulah inti dari autodan decltypeatau dalam kasus ini self.
Ralph Tandetzky
1
template<typename T>class Self{protected: typedef T self;}; class WITH_SELF(Foo) : public Bar, private Baz {};akan lebih sederhana dan akan memungkinkan kontrol yang lebih tepat atas warisan - ada alasan yang menentang?
Aconcagua
@mmmmmmmm, jika Anda belum belajar untuk mengapresiasi secara mendalam prinsip "Jangan Ulangi Diri Anda", kemungkinan Anda belum membuat kode yang cukup / serius. "Kekacauan" ini (sebenarnya jauh dari itu) adalah perbaikan yang cukup elegan dalam konteks berbicara tentang fitur bahasa yang tidak elegan (atau kesalahan, bahkan kekurangan oleh beberapa tindakan ketat).
Sz.
38

Solusi yang mungkin (karena Anda masih harus menulis jenisnya sekali):

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

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

Untuk versi yang lebih aman, kami dapat memastikan bahwa Tsebenarnya berasal dari Self<T>:

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

Perhatikan bahwa static_assertdi dalam fungsi anggota mungkin satu-satunya cara untuk memeriksa, karena tipe yang diteruskan std::is_base_ofharus lengkap.

Sebastian Hoffmann
sumber
4
Tidak perlu typenamedi typedef. Dan karena ini tidak mengurangi jumlah redundansi, menurut saya ini bukan alternatif yang layak.
Konrad Rudolph
Ini memiliki masalah pengulangan Foonama yang persis sama .
Bartek Banachewicz
6
Ini adalah sedikit lebih baik daripada pendekatan asli, meskipun, karena pengulangan sangat dekat bersama-sama. Bukan solusi untuk pertanyaan, tetapi +1 untuk upaya yang layak untuk solusi kasus terbaik.
Balapan Ringan di Orbit
4
Saya menggunakan solusi itu beberapa kali, dan itu memiliki satu hal yang BURUK: ketika kemudian diturunkan Foo, Anda harus: (1) menyebarkan T ke atas ke keturunan daun, atau (2) ingat untuk mewarisi dari SelfT berkali-kali , atau (3) menerima bahwa semua hal anak-anak menjadi Base .. dapat digunakan, tetapi bersahaja.
quetzalcoatl
@quetzalcoatl: Karena saya mencoba meniru selfdaripada static, itu tidak masalah.
Balapan Ringan di Orbit
33

Anda dapat menggunakan makro sebagai ganti deklarasi kelas biasa, yang akan melakukannya untuk Anda.

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

Dan kemudian gunakan seperti

CLASS_WITH_SELF(Foo) 
};

#define END_CLASS }; mungkin akan membantu keterbacaan.


Anda juga dapat menggunakan @ Paranaix Selfdan menggunakannya (mulai menjadi sangat hack)

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

class WITH_SELF(Foo) {
};
Bartek Banachewicz
sumber
18
EWWWW END_CLASS. Itu sama sekali tidak perlu.
Anak Anjing
31
@DeadG Saya rasa beberapa orang mungkin menyukai lebih banyak konsistensi; lagipula, penggunaan makro pertama tidak berakhir dengan {, jadi }is "hang", yang mungkin tidak disukai oleh editor teks juga.
Bartek Banachewicz
6
Ide bagus, tetapi meskipun saya tidak secara fundamental menentang makro, saya hanya akan menerima penggunaannya di sini jika ia meniru C ++ scoping, yaitu jika dapat digunakan sebagai CLASS_WITH_SELF(foo) { … };- dan saya pikir itu tidak mungkin dicapai.
Konrad Rudolph
2
@KonradRudol Saya telah menambahkan cara untuk melakukan itu juga. Bukannya saya suka, hanya demi kelengkapan
Bartek Banachewicz
1
Ada beberapa masalah dengan pendekatan itu. Pertama tidak memungkinkan Anda untuk membuat kelas mewarisi dengan mudah (kecuali Anda menggunakan parameter makro lain), dan kedua memiliki semua masalah mewarisi dari yang Selfdimilikinya.
Bartek Banachewicz
31

Saya tidak memiliki bukti positif tetapi saya pikir itu tidak mungkin. Berikut ini gagal - karena alasan yang sama dengan upaya Anda - dan saya pikir itu yang terjauh yang bisa kita dapatkan:

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

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

Pada dasarnya, apa ini menunjukkan bahwa ruang lingkup di mana kita ingin mendeklarasikan typedef kami hanya memiliki tidak akses (baik itu langsung maupun tidak langsung) untuk this, dan tidak ada lainnya (compiler independen) cara untuk mendapatkan dengan jenis kelas atau nama.

Konrad Rudolph
sumber
4
Apakah ini mungkin dengan pengurangan tipe pengembalian C ++ 1y?
dyp
4
@dyp Untuk tujuan jawaban saya yang tidak akan mengubah apa pun. Kesalahan di sini bukan pada jenis hasil akhir, melainkan dalam pemanggilan.
Konrad Rudolph
1
@quetzalcoatl: Bagian dalam dari decltypeadalah konteks yang tidak dievaluasi, jadi memanggil fungsi anggota bukanlah masalah (yang tidak akan dicoba)
Balapan Ringan di Orbit
1
@TomKnapen Cobalah dengan dentang, dan itu akan gagal. Fakta bahwa itu diterima oleh GCC adalah bug, sejauh yang saya ketahui.
4
FWIW, struct S { int i; typedef decltype(i) Int; };berfungsi meskipun imerupakan anggota data non-statis. Ini berfungsi karena decltypememiliki pengecualian khusus di mana nama sederhana tidak dievaluasi sebagai ekspresi. Tetapi saya tidak dapat memikirkan cara apa pun untuk menggunakan kemungkinan ini dengan cara menjawab pertanyaan itu.
21

Apa yang berfungsi baik di GCC dan clang adalah membuat typedef yang merujuk ke thisdengan menggunakan thistipe trailing-return dari fungsi typedef. Karena ini bukan deklarasi fungsi anggota statis, penggunaan dari thisditoleransi. Anda kemudian dapat menggunakan typedef itu untuk mendefinisikan self.

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

struct Foo {
    DEFINE_SELF();
};

struct Bar {
    DEFINE_SELF();
};

Sayangnya, pembacaan standar yang ketat mengatakan bahwa ini pun tidak valid. Apa yang dilakukan clang adalah pemeriksaan yang thistidak digunakan dalam definisi fungsi anggota statis. Dan di sini, memang tidak. GCC tidak keberatan jika thisdigunakan dalam jenis kembali-akhir apa pun jenis fungsinya, GCC mengizinkannya bahkan untuk staticfungsi anggota. Namun, apa yang sebenarnya dibutuhkan standar adalah yang thistidak digunakan di luar definisi fungsi anggota non-statis (atau inisialisasi anggota data non-statis). Intel melakukannya dengan benar dan menolak ini.

Mengingat bahwa:

  • this hanya diperbolehkan dalam penginisialisasi anggota data non-statis dan fungsi anggota non-statis ([expr.prim.general] p5),
  • anggota data non-statis tidak dapat memiliki jenisnya disimpulkan dari inisialisasi ([dcl.spec.auto] p5),
  • fungsi anggota non-statis hanya dapat dirujuk dengan nama yang tidak memenuhi syarat dalam konteks pemanggilan fungsi ([expr.ref] p4)
  • fungsi anggota non-statis hanya dapat dipanggil dengan nama yang tidak memenuhi syarat, bahkan dalam konteks yang tidak dievaluasi, ketika thisdapat digunakan ([over.call.func] p3),
  • referensi ke fungsi anggota non-statis dengan nama yang memenuhi syarat atau akses anggota memerlukan referensi ke tipe yang didefinisikan

Saya rasa saya dapat secara meyakinkan mengatakan bahwa tidak ada cara sama sekali untuk mengimplementasikan selftanpa memasukkan dalam beberapa cara, di suatu tempat, nama tipe.

Sunting : Ada kekurangan dalam alasan saya sebelumnya. "Fungsi anggota non-statis hanya dapat dipanggil dengan nama yang tidak memenuhi syarat, bahkan dalam konteks yang tidak dievaluasi, saat ini dapat digunakan ([over.call.func] p3)," salah. Apa yang sebenarnya dikatakannya adalah

Jika kata kunci this(9.3.2) berada dalam ruang lingkup dan mengacu pada kelas T, atau kelas turunan dari T, maka argumen objek tersirat adalah (*this). Jika kata kunci thistidak berada dalam ruang lingkup atau merujuk ke kelas lain, maka objek tipe yang dibuat-buat Tmenjadi argumen objek tersirat. Jika daftar argumen ditambah dengan objek yang dibuat-buat dan resolusi kelebihan beban memilih salah satu fungsi anggota non-statis, Tpanggilan menjadi tidak benar.

Di dalam fungsi anggota statis, thismungkin tidak muncul, tetapi masih ada.

Namun, berdasarkan komentar, di dalam fungsi anggota statis, transformasi f()menjadi (*this).f()tidak akan dilakukan, dan itu tidak dilakukan, maka [expr.call] p1 dilanggar:

[...] Untuk pemanggilan fungsi anggota, ekspresi postfix harus berupa akses anggota kelas implisit (9.3.1, 9.4) atau eksplisit (5.2.5) yang [...]

karena tidak akan ada akses anggota. Jadi itu pun tidak akan berhasil.


sumber
Saya pikir [class.mfct.non-static] / 3 mengatakan bahwa _self_fn_1()"diubah" menjadi (*this)._self_fn_1(). Tidak yakin apakah itu membuatnya ilegal.
dyp
@dyp Dikatakan "digunakan dalam anggota kelas Xdalam konteks di mana thisdapat digunakan", jadi saya tidak berpikir bahwa transformasi dilakukan.
1
Tapi kemudian itu bukan akses anggota kelas implisit maupun eksplisit ..? [expr.call] / 1 "Untuk pemanggilan fungsi anggota, ekspresi postfix harus berupa akses anggota kelas yang implisit atau eksplisit [...]"
dyp
(Maksud saya, apa yang terjadi jika Anda memilikinya auto _self_fn_1() -> decltype(*this); auto _self_fn_1() const -> decltype(*this);?)
dyp
@dyp [expr.call] / 1 adalah poin yang bagus, saya harus melihat lebih dekat. Tentang constkelebihan beban, meskipun: itu bukan masalah. 5.1p3 secara khusus telah dimodifikasi untuk juga diterapkan pada fungsi anggota statis, dan menyatakan tipe thisis Foo*/ Bar*(tanpa const), karena tidak ada constdalam deklarasi _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
};

ini tidak bekerja pada jenis template, karena self_checktidak dipanggil, jadi static_asserttidak dievaluasi.

Kami dapat melakukan beberapa peretasan untuk membuatnya berfungsi templatejuga, tetapi memiliki biaya waktu pengoperasian yang kecil.

#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

ruang kosong structberukuran 1 byte dibuat di kelas Anda. Jika tipe Anda dibuat instance-nya, selfdiuji terhadap.

Yakk - Adam Nevraumont
sumber
Itu juga tidak buruk!
Balapan Ringan di Orbit
@LightnessRacesinOrbit sekarang dengan templateopsi dukungan kelas.
Yakk - Adam Nevraumont
Saya benar-benar memikirkan hal ini saat saya pulang kerja kemarin. Anda mengalahkan saya untuk itu :). Saya menyarankan untuk mendeklarasikan self_check () sebagai inline, untuk menghindari masalah penautan (simbol yang sama Foo :: self_check () ditemukan di beberapa file objek).
babi
1
@theswine: 9.3 / 2 adalah indeks paragraf dalam standar C ++, yang menjamin bahwa fungsi anggota kelas yang ditentukan dalam isi definisi kelas sudah, secara implisit inline,. Artinya Anda tidak perlu menulis inlinesama sekali. Jadi jika Anda telah menulis inlinedi depan setiap definisi fungsi anggota kelas untuk seluruh karir Anda, Anda dapat berhenti sekarang;)
Lightness Races di Orbit
2
@LightnessRacesinOrbit Oh, sebenarnya saya dulu. Terima kasih, itu akan menghemat beberapa pengetikan di masa mendatang :). Saya selalu kagum dengan betapa saya tidak tahu tentang C ++.
babi
11

Saya juga berpikir itu tidak mungkin, inilah upaya menarik lainnya yang gagal tetapi IMHO yang menghindari this-akses:

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

yang gagal karena C ++ mengharuskan Anda untuk memenuhi syarat self_fdengan kelas ketika Anda ingin mengambil alamatnya :(

Daniel Frey
sumber
Dan masalah yang sama terjadi dengan int T::*penunjuk biasa ke variabel anggota. Dan int self_var; typedef decltype(&self_var) self_ptrtidak berhasil juga, itu hanya biasa int*.
MSalters
9

Saya baru-baru ini menemukan bahwa *thisdiperbolehkan dalam brace-or-equal-initializer . Dijelaskan dalam § 5.1.1 ( dari draft kerja n3337 ):

3 [..] Tidak seperti ekspresi objek dalam konteks lain, *thistidak diperlukan tipe lengkap untuk tujuan akses anggota kelas (5.2.5) di luar badan fungsi anggota. [..]

4 Jika tidak, jika deklarator-anggota mendeklarasikan anggota data non-statis (9.2) dari kelas X, ekspresi tersebut thisadalah prvalue tipe "pointer ke X" dalam brace-or-equal-initializer opsional . Itu tidak akan muncul di tempat lain di anggota-deklarator .

5 Ekspresi thistidak akan muncul dalam konteks lain. [ Contoh:

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

- contoh akhir ]

Dengan mengingat hal itu, kode berikut:

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

melewati Daniel Frey static_assert .

Live example

Komunitas
sumber
Anda memiliki variabel berguna mengganggu testmeskipun
MM
@Matt Benar, tapi saya masih menganggapnya menarik.
1
Ini bisa berhasil tanpanya = this, bukan? Dan mengapa tidak hanyausing self = Foo*;
user362515
1
Kami tidak mendapatkan apa pun di sini pasti, karena kami harus mendeklarasikan testmenjadi tipe, um, Foo *!
Paul Sanders
4

Kecuali jika tipe tersebut harus merupakan tipe anggota dari kelas yang melingkupi, Anda dapat mengganti penggunaannya selfdengan decltype(*this). Jika Anda menggunakannya di banyak tempat di kode Anda, Anda dapat menentukan makro SELFsebagai berikut:

#define SELF decltype(*this)
TAS
sumber
2
Dan Anda tidak dapat menggunakannya di luar kelas, atau di kelas bersarang
Drax
1
@Drax: Seharusnya tidak tersedia di luar kelas.
Ben Voigt
@BenVoigt Tapi itu seharusnya tersedia di kelas bersarang yang IMO kasus penggunaan yang paling menarik.
Drax
1
Saya kira tidak. Tidakkah seharusnya selfmengacu pada kelas yang langsung melingkupi dan bukan kelas luar? Tapi saya tidak tahu php dengan baik.
Ben Voigt
1
@LightnessRacesinOrbit: Saya rasa kode dan kesalahan seharusnya mengatakan "PHP tidak memiliki tipe bersarang"?
Ben Voigt
1

Berikan versi saya. Yang terbaik adalah penggunaannya sama dengan kelas native. Namun, ini tidak berfungsi untuk kelas template.

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); }
};
pengguna1899020
sumber
1

Berdasarkan jawaban dari hvd, saya menemukan bahwa satu-satunya hal yang hilang adalah menghapus referensi, itulah sebabnya pemeriksaan std :: is_same gagal (b / c tipe yang dihasilkan sebenarnya adalah referensi ke tipe). Sekarang makro tanpa parameter ini dapat melakukan semua pekerjaan. Contoh kerja di bawah ini (saya menggunakan GCC 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";
}
niksbenik.dll
sumber
Itu tidak dapat dikompilasi pada kompiler lain selain GCC.
zedu
0

Saya akan mengulangi solusi yang jelas "harus melakukannya sendiri". Ini adalah versi kode C ++ 11 yang ringkas, yang berfungsi dengan kelas sederhana dan template kelas:

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

Anda dapat melihatnya beraksi di ideone . Asal, yang mengarah ke hasil ini di bawah:

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

struct XYZ {
    DECLARE_SELF(XYZ)
};

Ini memiliki masalah yang jelas dengan menyalin-menempel kode ke kelas yang berbeda dan lupa mengubah XYZ, seperti di sini:

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

Pendekatan pertama saya tidak terlalu orisinal - membuat fungsi, seperti ini:

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

Agak panjang, tapi tolong bersabarlah dengan saya di sini. Ini memiliki keuntungan bekerja di C ++ 03 tanpa decltype, karena __self_check_helperfungsi tersebut digunakan untuk menyimpulkan jenis this. Juga, tidak ada static_assert, tetapi sizeof()trik yang digunakan sebagai gantinya. Anda bisa membuatnya lebih pendek untuk C ++ 0x. Sekarang ini tidak akan berfungsi untuk template. Juga, ada masalah kecil dengan makro yang tidak mengharapkan titik koma di akhir, jika dikompilasi dengan pedantic, itu akan mengeluh tentang titik koma ekstra yang tidak perlu (atau Anda akan ditinggalkan dengan makro yang tampak aneh tidak berakhir dengan titik koma di badan XYZdan ABC).

Melakukan pemeriksaan pada Typeyang diteruskan DECLARE_SELFbukanlah suatu pilihan, karena itu hanya akan memeriksa XYZkelas (yang ok), tidak menyadari ABC(yang memiliki kesalahan). Dan kemudian aku tersadar. Solusi penyimpanan tanpa biaya tambahan yang bekerja dengan template:

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 

Ini hanya membuat pernyataan statis pada nilai enum unik (atau setidaknya unik jika Anda tidak menulis semua kode Anda pada satu baris), tidak ada tipu daya pembanding tipe yang digunakan, dan berfungsi sebagai pernyataan statis, bahkan di template . Dan sebagai bonus - titik koma terakhir sekarang diperlukan :).

Saya ingin berterima kasih kepada Yakk karena telah memberi saya inspirasi yang baik. Saya tidak akan menulis ini tanpa melihat jawabannya terlebih dahulu.

Diuji dengan VS 2008 dan g ++ 4.6.3. Memang, dengan contoh XYZdan ABCitu mengeluh:

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

Sekarang jika kita membuat template ABC:

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

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

Kita akan mendapatkan:

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

Hanya pemeriksaan nomor baris yang dipicu, karena pemeriksaan fungsi tidak dikompilasi (seperti yang diharapkan).

Dengan C ++ 0x (dan tanpa garis bawah jahat), Anda hanya perlu:

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

Saya percaya bahwa bit CStaticAssert sayangnya masih diperlukan karena menghasilkan sebuah tipe, yang diketikkan dalam tubuh template (saya kira hal yang sama tidak dapat dilakukan dengan static_assert). Keuntungan dari pendekatan ini masih biaya nolnya.

babi
sumber
Anda pada dasarnya menerapkan ulang di static_assertsini, bukan? Selain itu, kode lengkap Anda tidak valid karena Anda menggunakan pengenal ilegal (dicadangkan).
Konrad Rudolph
@KonradRudol Ya, memang begitu. Saya tidak memiliki C ++ 0x saat itu, jadi saya menerapkan ulang static_assert untuk memberikan jawaban lengkap. Saya mengatakan itu dalam jawaban. Apakah itu tidak valid? Bisakah Anda menunjukkan caranya? Ini dikompilasi dengan baik, saya menggunakannya sekarang.
babi
1
Pengenal tidak valid karena C ++ menyimpan semuanya dengan garis bawah di depan diikuti dengan huruf besar, serta dua garis bawah di depan dalam cakupan global, untuk kompiler. Kode pengguna tidak boleh menggunakannya, tetapi tidak semua kompiler akan menandainya sebagai kesalahan.
Konrad Rudolph
@KonradRudol Begitu, saya tidak tahu itu. Saya memiliki banyak kode yang menggunakan itu, tidak pernah mengalami masalah dengan itu baik di Linux / Mac / Windows. Tapi saya rasa itu bagus untuk diketahui.
babi
0

Saya tidak tahu semua tentang template aneh ini, bagaimana dengan sesuatu yang sangat sederhana:

#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

Pekerjaan selesai, kecuali Anda tidak tahan dengan beberapa makro. Anda bahkan dapat menggunakan CLASSNAMEuntuk mendeklarasikan konstruktor Anda (dan, tentu saja, destruktor).

Demo langsung .

Paul Sanders
sumber
1
Ini memiliki efek yang cukup jelas tentang bagaimana kelas dapat / harus digunakan kemudian
Lightness Races di Orbit
@LightnessRacesinOrbit Bagaimana bisa? Saya tidak melihatnya. Saya lakukan, sebagai refleksi, menghapus kalimat terakhir dari posting asli saya. Apa yang saya miliki di sana awalnya mungkin membuat Anda berpikir demikian.
Paul Sanders