Czy mogę zaimplementować autonomiczny typ składowy „self” w C ++?


101

C ++ nie ma odpowiednika słowa kluczowego PHPself , które ocenia typ otaczającej klasy.

Łatwo jest sfałszować to na podstawie klasy:

struct Foo
{
   typedef Foo self;
};

ale musiałem napisać Fooponownie. Może któregoś dnia się pomylę i wywołam cichy błąd.

Czy mogę użyć kombinacji decltypeprzyjaciół i przyjaciół, aby ta praca działała „autonomicznie”? Próbowałem już następujących rzeczy, ale thisnie są one prawidłowe w tym miejscu:

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

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

(Nie mam zamiaru martwić się o odpowiednik static, który robi to samo, ale z późnym wiązaniem).


9
this_tbyłoby prawdopodobnie bardziej dopasowane do zwykłego nazewnictwa w C ++.
Bartek Banachewicz

3
@BartekBanachewicz: or this_type
PlasmaHH

10
@Praetorian, nie pamiętam, czy to była propozycja, czy nie, ale ktoś zasugerował auto()i ~auto()dla lekarzy / lekarzy. Co najmniej interesujące. Może jeśli zostanie użyty do tego celu, typedef auto self;ale wydaje mi się to trochę szkicowe.
chris

11
Szczerze mówiąc, gdybym miał zasugerować składnię, która umożliwiłaby to, prawdopodobnie byłaby to decltype(class), być może z decltype(struct)odpowiednikiem. Jest to o wiele wyraźniejsze niż tylko autow konkretnym kontekście i nie widzę żadnych problemów z dopasowaniem go do języka, na którym się opiera decltype(auto).
chris

11
Ponieważ chcesz uniknąć błędów, możesz skonfigurować fikcyjną funkcję składową z static_assert, na przykład void _check() { static_assert(std::is_same<self&, decltype(*this)>::value, "Correct your self type"); }Nie działa z szablonami klas ...
milleniumbug

Odpowiedzi:


39

Oto, jak możesz to zrobić bez powtarzania typu 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;
    }
};

Jeśli chcesz wyprowadzić z Footego makra, powinieneś użyć makra WITH_SELF_DERIVEDw następujący sposób:

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

Możesz nawet zrobić wielokrotne dziedziczenie z dowolną liczbą klas bazowych (dzięki wariadycznym szablonom i wariadycznym makrom):

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

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

Sprawdziłem to, aby działać na gcc 4.8 i clang 3.4.


18
Myślę, że odpowiedź brzmi "nie, ale Ralph może!" ;)
Lightness Races in Orbit

3
W jaki sposób jest to w jakikolwiek sposób lepsze od zwykłego umieszczenia w nim czcionki? I Boże, dlaczego miałbyś w ogóle potrzebować tego kroju? Czemu?
Miles Rout

7
@MilesRout To jest pytanie dotyczące pytania, a nie odpowiedzi. W wielu przypadkach w rozwoju oprogramowania (a zwłaszcza w utrzymaniu) pomocne jest unikanie redundancji w kodzie, tak aby zmiana czegoś w jednym miejscu nie wymagała zmiany kodu w innym miejscu. O to chodzi w autoi decltypelub w tym przypadku self.
Ralph Tandetzky

1
template<typename T>class Self{protected: typedef T self;}; class WITH_SELF(Foo) : public Bar, private Baz {};byłoby prostsze i umożliwiłoby bardziej precyzyjną kontrolę nad dziedziczeniem - czy są powody przeciw?
Aconcagua

@mmmmmmmm, jeśli nie nauczyłeś się głęboko doceniać zasady „Nie powtarzaj się”, prawdopodobnie nie zakodowałeś jeszcze wystarczająco / poważnie. Ten „bałagan” (a właściwie wręcz przeciwnie) jest dość eleganckim rozwiązaniem w kontekście mówienia o nieeleganckiej właściwości języka (lub niewłaściwości, a nawet niedoborze według pewnych ścisłych miar).
Sz.

38

Możliwe obejście (ponieważ nadal musisz raz wpisać typ):

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

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

Aby uzyskać bezpieczniejszą wersję, możemy zapewnić, że Tfaktycznie pochodzi z Self<T>:

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

Zauważ, że static_assertfunkcja wewnątrz elementu członkowskiego jest prawdopodobnie jedynym sposobem sprawdzenia, ponieważ przekazywane typy std::is_base_ofmuszą być kompletne.


4
Nie ma potrzeby typenamew typedef. A ponieważ nie zmniejsza to liczby zwolnień, nie sądzę, aby była to realna alternatywa.
Konrad Rudolph

Ma dokładnie ten sam problem z powtarzaniem Foonazwy.
Bartek Banachewicz

6
Jest to jednak nieznacznie lepsze niż oryginalne podejście, ponieważ powtórzenia są bardzo blisko siebie. Nie jest to odpowiedź na pytanie, ale +1 za godną próbę najlepszego obejścia.
Wyścigi lekkości na orbicie

4
Użyłem tego rozwiązania kilka razy i ma jedną ZŁĄ rzecz: kiedy później wyprowadzasz z Foo, musisz albo: (1) propagować T w górę do potomka liścia, albo (2) pamiętać o wielokrotnym dziedziczeniu po SelfT lub (3) zaakceptuj, że wszystkie dzieci uważają za Bazę… użyteczne, ale nieładne.
quetzalcoatl

@quetzalcoatl: Ponieważ próbuję replikować, selfa nie static, nie stanowi to problemu.
Wyścigi lekkości na orbicie

33

Możesz użyć makra zamiast zwykłej deklaracji klasy, która zrobi to za Ciebie.

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

A potem użyj like

CLASS_WITH_SELF(Foo) 
};

#define END_CLASS }; prawdopodobnie pomogłoby w czytelności.


Możesz także wziąć @ Paranaix Selfi użyć go (zaczyna robić się naprawdę hakerski)

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

class WITH_SELF(Foo) {
};

18
EWWWW END_CLASS. To zupełnie niepotrzebne.
Puppy

31
@DeadMG Myślę, że niektórym osobom może spodobać się większa spójność; w końcu pierwsze użycie makra nie kończy się na {, więc }„zawiesza się”, czego edytorzy tekstu prawdopodobnie by nie lubili.
Bartek Banachewicz

6
Niezły pomysł, ale chociaż zasadniczo nie jestem przeciwnikiem makr, zaakceptowałbym jego użycie tutaj tylko wtedy, gdyby naśladował zakres C ++, tj. Gdyby był użyteczny jako CLASS_WITH_SELF(foo) { … };- i myślę, że jest to niemożliwe do osiągnięcia.
Konrad Rudolph

2
@KonradRudolph Dodałem też sposób, aby to zrobić. Nie to, że mi się to podoba, tak dla zupełności
Bartek Banachewicz

1
Takie podejście wiąże się jednak z pewnymi problemami. Pierwsza z nich nie pozwala na łatwe dziedziczenie klasy (chyba że użyjesz innego parametru makra), a druga ma wszystkie problemy z dziedziczeniem po niej Self.
Bartek Banachewicz

31

Nie mam żadnych pozytywnych dowodów, ale myślę, że to niemożliwe. Poniższe zawodzi - z tego samego powodu co twoja próba - i myślę, że to najdalej, co możemy osiągnąć:

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

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

Zasadniczo pokazuje to, że zakres, w którym chcemy zadeklarować naszą typedef, po prostu nie ma dostępu (ani bezpośredniego, ani pośredniego) do thisi nie ma innego (niezależnego od kompilatora) sposobu na dotarcie do typu lub nazwy klasy.


4
Czy będzie to możliwe w przypadku dedukcji typu zwracanego w C ++ 1y?
dyp

4
@dyp Dla mojej odpowiedzi to niczego nie zmieni. Błąd tutaj nie znajduje się w końcowym typie zwracanym, jest w wywołaniu.
Konrad Rudolph

1
@quetzalcoatl: Wnętrze decltypejest niedocenianym kontekstem, więc wywołanie funkcji członkowskiej nie stanowi problemu (nie zostanie podjęta próba)
Lightness Races in Orbit

1
@TomKnapen Spróbuj z clang, a to się nie powiedzie. O ile mi wiadomo, fakt, że jest akceptowany przez GCC, jest błędem.

4
FWIW, struct S { int i; typedef decltype(i) Int; };działa, mimo że nie ijest statycznym składnikiem danych. Działa, ponieważ decltypema specjalny wyjątek, w którym prosta nazwa nie jest oceniana jako wyrażenie. Ale nie mogę wymyślić żadnego sposobu wykorzystania tej możliwości w sposób, który odpowiadałby na pytanie.

21

To, co działa zarówno w GCC, jak i clang, polega na utworzeniu typedef, do którego odwołuje się thispoprzez użycie thisw końcowym zwracanym typie funkcji typedef. Ponieważ nie jest to deklaracja statycznej funkcji składowej, użycie thisjest tolerowane. Następnie możesz użyć tego typu do zdefiniowania self.

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

struct Foo {
    DEFINE_SELF();
};

struct Bar {
    DEFINE_SELF();
};

Niestety, ścisła lektura normy mówi, że nawet to nie jest ważne. Clang sprawdza, thisczy nie jest używany w definicji statycznej funkcji składowej. A tutaj rzeczywiście tak nie jest. GCC nie ma nic przeciwko, jeśli thisjest używane w typie końcowym zwracanym niezależnie od rodzaju funkcji, pozwala na to nawet dla staticfunkcji składowych. Jednak standard faktycznie wymaga tego, aby thisnie był używany poza definicją niestatycznej funkcji składowej (lub niestatycznego inicjatora składowej danych). Intel ma rację i odrzuca to.

Jeśli się uwzględni:

  • this jest dozwolone tylko w statycznych inicjatorach składowych danych i niestatycznych funkcjach składowych ([wyr.prim.general] p5),
  • niestatyczne składowe danych nie mogą mieć ich typu wywnioskowanego z inicjatora ([dcl.spec.auto] p5),
  • do niestatycznych funkcji składowych można się odwoływać tylko przez niekwalifikowaną nazwę w kontekście wywołania funkcji ([wyraż.ref] p4)
  • niestatyczne funkcje składowe mogą być wywoływane tylko przez niekwalifikowaną nazwę, nawet w niedocenianych kontekstach, kiedy thismożna ich użyć ([over.call.func] p3),
  • odwołanie do niestatycznej funkcji składowej przez nazwę kwalifikowaną lub dostęp do elementu członkowskiego wymaga odwołania do definiowanego typu

Myślę, że mogę definitywnie powiedzieć, że nie ma żadnego sposobu na zaimplementowanie selfbez dołączenia gdzieś nazwy typu.

Edycja : W moim wcześniejszym rozumowaniu jest błąd. „Niestatyczne funkcje składowe mogą być wywoływane tylko przez niekwalifikowaną nazwę, nawet w niedocenianych kontekstach, kiedy można tego użyć ([over.call.func] p3)” jest niepoprawne. To, co faktycznie mówi, to

Jeśli słowo kluczowe this(9.3.2) znajduje się w zakresie i odnosi się do klasy Tlub klasy pochodnejT , to domniemany argument obiektu jest (*this). Jeśli słowo kluczowe thisnie znajduje się w zakresie lub odnosi się do innej klasy, to wymyślony obiekt typu Tstaje się niejawnym argumentem obiektu. Jeśli lista argumentów jest powiększona przez wymyślony obiekt, a rozwiązanie przeciążenia wybiera jedną z niestatycznych funkcji składowych T, wywołanie jest źle sformułowane.

Wewnątrz statycznej funkcji składowej thismoże się nie pojawiać, ale nadal istnieje.

Jednak zgodnie z komentarzami wewnątrz statycznej funkcji składowej transformacja f()(*this).f() składowej do nie zostanie wykonana, a jeśli nie zostanie wykonana, naruszone jest [wyrażenie] p1:

[...] W przypadku wywołania funkcji składowej wyrażenie przyrostkowe powinno być niejawnym (9.3.1, 9.4) lub jawnym dostępem do elementu członkowskiego klasy (5.2.5), którego [...]

ponieważ nie byłoby dostępu dla członków. Więc nawet to nie zadziała.


Myślę, że [class.mfct.non-static] / 3 mówi, że _self_fn_1()jest "przekształcany" w (*this)._self_fn_1(). Nie jestem jednak pewien, czy to czyni to nielegalnym.
dyp

@dyp Mówi się, że „jest używany w elemencie klasy Xw kontekście, w którym thismożna go użyć”, więc nie sądzę, aby wykonywana była transformacja.

1
Ale wtedy nie jest to ani niejawny, ani jawny dostęp do elementu członkowskiego klasy ..? [expr.call] / 1 "W przypadku wywołania funkcji składowej wyrażenie postfix powinno być niejawnym lub jawnym dostępem do elementu klasy [...]"
dyp

(To znaczy, co się stanie, gdy to zrobisz auto _self_fn_1() -> decltype(*this); auto _self_fn_1() const -> decltype(*this);?)
dyp

@dyp [expr.call] / 1 to dobra uwaga, będę musiał przyjrzeć się bliżej. constJednak o przeciążeniach: to nie jest problem. 5.1p3 został specjalnie zmodyfikowany, aby mieć również zastosowanie do statycznych funkcji składowych i mówi, że typ thisjest Foo*/ Bar*(bez const), ponieważ nie ma constw deklaracji _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
};

to nie działa na typach szablonów, ponieważ self_checknie jest nazywany, więcstatic_assert nie jest oceniane.

Możemy zrobić kilka hacków, aby działało również przez templates, ale ma to niewielki koszt w czasie wykonywania.

#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

structw klasie tworzony jest pusty bajt o rozmiarze 1. Jeśli tworzony jest typ, selfjest testowany przeciwko.


To też nie jest złe!
Wyścigi lekkości na orbicie

@LightnessRacesinOrbit teraz z templateopcjami obsługi klas.
Yakk - Adam Nevraumont

Myślałem o tym dokładnie, wychodząc wczoraj z pracy. Pokonałeś mnie :). Sugerowałbym zadeklarowanie self_check () jako inline, aby uniknąć problemów z łączeniem (ten sam symbol Foo :: self_check (), który można znaleźć w wielu plikach obiektowych).
świnia

1
@theswine: 9.3 / 2 jest indeksem akapitu w standardzie C ++, który gwarantuje, że funkcje składowe klasy zdefiniowane w treści definicji klasy są już domyślnie inline. Oznacza to, że nie musisz inlinew ogóle pisać . Więc jeśli pisałeś inlineprzed każdą taką definicją funkcji członka klasy przez całą swoją karierę, możesz teraz przestać;)
Lightness Races in Orbit

2
@LightnessRacesinOrbit Och, właściwie to byłem. Dziękuję, zaoszczędzi mi to trochę pisania w przyszłości :). Zawsze jestem zdumiony tym, jak wiele nie wiem o C ++.
świnia

11

Myślę też, że to niemożliwe, oto kolejna nieudana, ale interesująca próba IMHO, która pozwala uniknąć this-dostępu:

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

co kończy się niepowodzeniem, ponieważ C ++ wymaga zakwalifikowania self_fsię do klasy, jeśli chcesz zająć jej adres :(


Ten sam problem występuje w przypadku zwykłego int T::*wskaźnika do zmiennej składowej. I int self_var; typedef decltype(&self_var) self_ptrteż nie działa, to po prostu normalne int*.
MSalters

9

Niedawno odkryłem, że *thisjest to dozwolone w inicjatorze nawiasów klamrowych lub równych . Opisane w § 5.1.1 ( z projektu roboczego n3337 ):

3 [..] W przeciwieństwie do wyrażenia obiektowego w innych kontekstach, *thisnie wymaga się, aby był kompletnym typem dla celów dostępu do składowej klasy (5.2.5) poza treścią funkcji składowej. [..]

4 W przeciwnym razie, jeśli deklarator elementu członkowskiego deklaruje niestatyczną składową danych (9.2) klasy X, wyrażenie thisjest wartością parametru „wskaźnik do X” w opcjonalnym inicjatorze nawiasu klamrowego lub równego . Nie pojawia się w innym miejscu w składniku deklaracji .

5 Wyrażenie thisnie pojawia się w żadnym innym kontekście. [ Przykład:

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

- przykład końca ]

Mając to na uwadze, poniższy 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, "" );
}

mija Daniel Frey's static_assert .

Live example


Masz irytujące bezużyteczne zmienną testchoć
MM

@Matt True, ale nadal uważam to za interesujące.

1
To mogłoby zadziałać bez = this, prawda? I dlaczego nie tylkousing self = Foo*;
user362515

1
Na pewno nic tu nie zyskaliśmy, bo musieliśmy zadeklarować, że jesteśmy testtypem, hm Foo *!
Paul Sanders,

4

Chyba że typ musi być typu członkiem klasy otaczającej można wymienić zastosowanie selfprzy decltype(*this). Jeśli używasz go w wielu miejscach w kodzie, możesz zdefiniować makro SELFw następujący sposób:

#define SELF decltype(*this)

2
I nie możesz tego używać poza klasą lub w klasach zagnieżdżonych
Drax

1
@Drax: To nie powinno być dostępne poza zajęciami.
Ben Voigt,

@BenVoigt Ale ma być dostępny w zagnieżdżonych klasach, co jest najciekawszym przypadkiem użycia IMO.
Drax,

1
Nie sądzę. Nie powinno selfodnosić się do bezpośrednio otaczającej klasy, a nie do klasy zewnętrznej? Ale ja nie znam tak dobrze PHP.
Ben Voigt,

1
@LightnessRacesinOrbit: Wydaje mi się, że kod i błąd mają powiedzieć "PHP nie ma zagnieżdżonych typów"?
Ben Voigt,

1

Podaj moją wersję. Najlepsze jest to, że jego użycie jest takie samo jak w klasie natywnej. Jednak nie działa w przypadku klas szablonów.

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

Opierając się na odpowiedzi hvd, stwierdziłem, że jedyną rzeczą, której brakowało, było usunięcie odwołania, dlatego sprawdzenie std :: is_same kończy się niepowodzeniem (b / c typ wynikowy jest w rzeczywistości odniesieniem do typu). Teraz to bez parametrów makro może wykonać całą pracę. Przykład roboczy poniżej (używam 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";
}

Nie kompiluje się na innych kompilatorach niż GCC.
zedu

0

Powtórzę oczywiste rozwiązanie polegające na „konieczności zrobienia tego samemu”. To jest zwięzła wersja kodu w C ++ 11, która działa zarówno z prostymi klasami, jak i szablonami klas:

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

Możesz to zobaczyć w akcji w ideone . Geneza prowadząca do tego wyniku jest poniżej:

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

struct XYZ {
    DECLARE_SELF(XYZ)
};

Ma to oczywisty problem z kopiowaniem i wklejaniem kodu do innej klasy i zapominaniem o zmianie XYZ, jak tutaj:

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

Moje pierwsze podejście nie było zbyt oryginalne - stworzenie funkcji takiej jak ta:

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

To trochę długie, ale proszę o wyrozumiałość. Ma to tę zaletę, że pracuje w C ++ 03 bez decltype, ponieważ __self_check_helperfunkcja służy do wywnioskowania typu this. Nie ma też static_assert, ale sizeof()zamiast tego stosuje się sztuczkę. Możesz go znacznie skrócić dla C ++ 0x. Teraz to nie zadziała w przypadku szablonów. Istnieje również niewielki problem z makrem, które nie oczekuje średnika na końcu, jeśli kompiluje się z pedantycznym, będzie narzekać na dodatkowy niepotrzebny średnik (lub pozostanie z dziwnie wyglądającym makrem nie kończącym się średnikiem w treści XYZi ABC).

Sprawdzenie tego, Typeco jest przekazywane, DECLARE_SELFnie jest opcją, ponieważ sprawdzałoby tylko XYZklasę (która jest w porządku), nieświadoma ABC(która ma błąd). I wtedy do mnie dotarło. Bezkosztowe rozwiązanie do magazynowania bez dodatkowych kosztów, które współpracuje z szablonami:

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 

To po prostu tworzy statyczne potwierdzenie na unikalnej wartości wyliczenia (lub przynajmniej unikalne w przypadku, gdy nie piszesz całego kodu w jednym wierszu), nie jest stosowana żadna sztuczka porównująca typy i działa jako asercja statyczna, nawet w szablonach . I jako bonus - ostatni średnik jest teraz wymagany :).

Chciałbym podziękować Yakkowi za dobrą inspirację. Nie napisałbym tego bez uprzedniego zobaczenia jego odpowiedzi.

Testowane z VS 2008 i g ++ 4.6.3. Rzeczywiście, z przykładem XYZi ABCnarzeka:

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

Teraz, jeśli zrobimy ABC szablonem:

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

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

Dostaniemy:

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

Wyzwolono tylko sprawdzenie numeru wiersza, ponieważ kontrola funkcji nie została skompilowana (zgodnie z oczekiwaniami).

W C ++ 0x (i bez złych znaków podkreślenia) wystarczyłoby:

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

Uważam, że bit CStaticAssert jest niestety nadal wymagany, ponieważ tworzy typ, który jest wpisany w treści szablonu (przypuszczam, że tego samego nie można zrobić static_assert). Zaletą tego podejścia jest nadal zerowy koszt.


Zasadniczo wdrażasz static_asserttutaj ponownie , prawda? Ponadto cały kod jest nieprawidłowy, ponieważ używasz nielegalnych (zastrzeżonych) identyfikatorów.
Konrad Rudolph

@KonradRudolph Tak, rzeczywiście tak jest. Nie mam w tej chwili C ++ 0x, więc ponownie zaimplementowałem static_assert, aby udzielić pełnej odpowiedzi. Mówię to w odpowiedzi. Czy to jest nieważne? Czy możesz wskazać jak? Skompilował się dobrze, używam go teraz.
świnia

1
Identyfikatory są nieprawidłowe, ponieważ C ++ rezerwuje wszystko z początkowym podkreśleniem, po którym następuje wielka litera, a także dwa wiodące podkreślenia w zakresie globalnym dla kompilatora. Kod użytkownika nie może go używać, ale nie wszystkie kompilatory oznaczą go jako błąd.
Konrad Rudolph,

@KonradRudolph Widzę, nie wiedziałem tego. Mam dużo kodu, który tego używa, nigdy nie miałem z tym problemów na Linuksie / Macu / Windowsie. Ale myślę, że dobrze jest wiedzieć.
świnia

0

Nie wiem wszystkiego o tych zwariowanych szablonach, a może coś super prostego:

#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

Praca wykonana, chyba że nie możesz znieść kilku makr. Możesz nawet użyć CLASSNAMEdo zadeklarowania konstruktora (-ów) (i oczywiście destruktora).

Demo na żywo .


1
Ma to dość wyraźny wpływ na to, jak klasa może / musi być następnie używana
Wyścigi lekkości na orbicie.

@LightnessRacesinOrbit Jak to? Nie widzę tego. Po namyśle usunąłem ostatnie zdanie z mojego oryginalnego postu. To, co tam miałem pierwotnie, mogło cię skłonić do myślenia.
Paul Sanders,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.