Posso implementar um tipo de membro `self` autônomo em C ++?

101

C ++ não possui o equivalente da palavra-chave do PHPself , que avalia o tipo da classe envolvente.

É muito fácil fingir em uma base por classe:

struct Foo
{
   typedef Foo self;
};

mas eu tive que escrever Foonovamente. Talvez um dia eu me engane e cause um bug silencioso.

Posso usar alguma combinação de decltypee amigos para tornar este trabalho "autônomo"? Já tentei o seguinte, mas thisnão é válido naquele lugar:

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

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

(Não vou me preocupar com o equivalente de static, que faz o mesmo, mas com ligação tardia.)

Lightness Races in Orbit
fonte
9
this_tprovavelmente estaria mais alinhado com a nomenclatura C ++ regular.
Bartek Banachewicz de
3
@BartekBanachewicz: ou this_type
PlasmaHH
10
@Praetorian, não me lembro se foi uma proposta ou não, mas alguém sugeriu auto()e ~auto()para ctors / dtors. Interessante para dizer o mínimo. Se usado para esse propósito, talvez typedef auto self;, mas parece um pouco vago para mim.
chris de
11
Honestamente, se eu fosse sugerir uma sintaxe para tornar isso possível, provavelmente seria decltype(class), talvez com um decltype(struct)equivalente. Isso é muito mais claro do que apenas autoem um contexto específico e não vejo problemas em se adequar à linguagem baseada em decltype(auto).
chris de
11
Já que você deseja evitar erros, você pode configurar uma função-membro fictícia com static_assert, como void _check() { static_assert(std::is_same<self&, decltype(*this)>::value, "Correct your self type"); }Não funciona com modelos de classe, embora ...
milleniumbug

Respostas:

38

Veja como você pode fazer isso sem repetir o tipo de 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;
    }
};

Se você deseja derivar Foo, deve usar a macro WITH_SELF_DERIVEDda seguinte maneira:

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

Você pode até fazer herança múltipla com quantas classes de base quiser (graças a modelos e macros variados):

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

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

Eu verifiquei que isso funciona no gcc 4.8 e no clang 3.4.

Ralph Tandetzky
fonte
18
Acho que a resposta é "não, mas Ralph pode!" ;)
Lightness Races in Orbit
3
Como isso é superior a simplesmente colocar o typedef lá? E Deus, por que você ainda precisa do typedef? Por quê?
Miles Rout
7
@MilesRout Esta é uma pergunta sobre a pergunta, não a resposta. Em muitos casos, no desenvolvimento de software (e especialmente na manutenção), é útil evitar redundâncias no código, de forma que alterar algo em um local não exija que você altere o código em outro. Esse é todo o ponto de autoe decltypeou, neste caso de self.
Ralph Tandetzky
1
template<typename T>class Self{protected: typedef T self;}; class WITH_SELF(Foo) : public Bar, private Baz {};teria sido mais simples e permitiria um controle mais preciso sobre a herança - algum motivo contra?
Aconcágua
@mmmmmmmm, se você não aprendeu a apreciar profundamente o princípio "Don't Repeat Yourself", é provável que ainda não tenha codificado o suficiente / sério ainda. Essa "desordem" (longe disso, na verdade) é uma solução bastante elegante no contexto de falar sobre uma característica de linguagem deselegante (ou característica inadequada, até mesmo deficiência por algumas medidas estritas).
Sz.
38

Uma possível solução alternativa (já que você ainda precisa escrever o tipo uma vez):

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

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

Para uma versão mais segura, podemos garantir que Trealmente deriva de Self<T>:

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

Observe que static_assertdentro de uma função de membro é provavelmente a única maneira de verificar, já que os tipos passados std::is_base_ofdevem ser completos.

Sebastian Hoffmann
fonte
4
Não há necessidade de typenameno typedef. E como isso não reduz o número de demissões, não acho que seja uma alternativa viável.
Konrad Rudolph
Ele tem exatamente o mesmo problema de repetir o Foonome.
Bartek Banachewicz de
6
Ele é marginalmente melhor do que a abordagem original, porém, desde que a repetição é muito próximo juntos. Não é uma solução para a questão, mas +1 para uma tentativa válida de uma solução alternativa na melhor das hipóteses.
Lightness Races in Orbit de
4
Eu usei essa solução algumas vezes, e ela tem uma coisa RUIM: quando mais tarde deriva de Foo, você tem que: (1) propagar o T para cima para o descendente de folha, ou (2) lembrar de herdar de SelfT muitas vezes , ou (3) aceitar que todas as coisas filhos sejam a Base .. utilizável, mas desagradável.
quetzalcoatl
@quetzalcoatl: Já que estou tentando replicar em selfvez de static, não há problema.
Lightness Races in Orbit de
33

Você pode usar uma macro em vez de uma declaração de classe regular, isso fará isso por você.

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

E então use como

CLASS_WITH_SELF(Foo) 
};

#define END_CLASS }; provavelmente ajudaria na legibilidade.


Você também pode pegar o @ Paranaix's Selfe usá-lo (começa a ficar muito hackeado)

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

class WITH_SELF(Foo) {
};
Bartek Banachewicz
fonte
18
EWWWW END_CLASS. Isso é totalmente desnecessário.
Cachorro de
31
@DeadMG Acho que algumas pessoas gostariam de mais consistência; afinal, o primeiro uso de macro não termina com {, então o }está "suspenso", o que os editores de texto provavelmente também não gostariam.
Bartek Banachewicz de
6
Boa ideia, mas embora eu não seja fundamentalmente contra as macros, só aceitaria seu uso aqui se ela imitasse o escopo do C ++, ou seja, se fosse utilizável como CLASS_WITH_SELF(foo) { … };- e acho que isso é impossível de conseguir.
Konrad Rudolph
2
@KonradRudolph Eu adicionei uma maneira de fazer isso também. Não que eu goste, apenas por uma questão de integridade
Bartek Banachewicz
1
No entanto, existem alguns problemas com essa abordagem. O primeiro não permite que você faça a classe herdar facilmente (a menos que você use outro (s) parâmetro (s) de macro) e, segundo, tem todos os problemas de herdar dela Self.
Bartek Banachewicz de
31

Não tenho evidências positivas, mas acho que é impossível. O seguinte falha - pelo mesmo motivo de sua tentativa - e acho que é o mais longe que podemos chegar:

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

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

Essencialmente, o que isso demonstra é que o escopo no qual queremos declarar nosso typedef simplesmente não tem acesso (seja direto ou indireto) thise não há outra maneira (independente do compilador) de chegar ao tipo ou nome da classe.

Konrad Rudolph
fonte
4
Isso será possível com a dedução do tipo de retorno do C ++ 1y?
dyp
4
@dyp Para o propósito da minha resposta, isso não mudará nada. O erro aqui não está no tipo de retorno à direita, mas na invocação.
Konrad Rudolph
1
@quetzalcoatl: As entranhas de decltypeé um contexto não avaliado, portanto, invocar a função de membro não é o problema (isso não será tentado)
Lightness Races in Orbit
1
@TomKnapen Experimente com clang e falhará. O fato de ser aceito pelo GCC é um bug, pelo que eu sei.
4
FWIW, struct S { int i; typedef decltype(i) Int; };funciona mesmo sendo ium membro de dados não estático. Funciona porque decltypetem uma exceção especial em que um nome simples não é avaliado como uma expressão. Mas não consigo pensar em nenhuma maneira de usar essa possibilidade de uma forma que responda à pergunta.
21

O que funciona tanto no GCC quanto no clang é criar um typedef que se refere ao thisuso thisno tipo de retorno à direita de um typedef de função. Uma vez que esta não é a declaração de uma função de membro estática, o uso de thisé tolerado. Você pode então usar esse typedef para definir self.

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

struct Foo {
    DEFINE_SELF();
};

struct Bar {
    DEFINE_SELF();
};

Infelizmente, uma leitura estrita da norma diz que mesmo isso não é válido. O que o clang faz é verificar o que thisnão é usado na definição de uma função de membro estática. E aqui, de fato não é. O GCC não se importa se thisfor usado em um tipo de retorno à direita, independentemente do tipo de função, ele permite até mesmo para staticfunções de membro. No entanto, o que o padrão realmente requer é que thisnão seja usado fora da definição de uma função de membro não estática (ou inicializador de membro de dados não estático). A Intel acerta e rejeita isso.

Dado que:

  • this só é permitido em inicializadores de membros de dados não estáticos e funções de membros não estáticos ([expr.prim.general] p5),
  • membros de dados não estáticos não podem ter seu tipo deduzido do inicializador ([dcl.spec.auto] p5),
  • funções de membro não estáticas só podem ser referidas por um nome não qualificado no contexto de uma chamada de função ([expr.ref] p4)
  • funções de membro não estáticas só podem ser chamadas por nome não qualificado, mesmo em contextos não avaliados, quando thispodem ser usadas ([over.call.func] p3),
  • uma referência a uma função de membro não estática por nome qualificado ou acesso de membro requer uma referência ao tipo que está sendo definido

Acho que posso dizer de forma conclusiva que não há maneira de implementar selfsem incluir de alguma forma, em algum lugar, o nome do tipo.

Edit : Há uma falha no meu raciocínio anterior. "funções de membro não estáticas só podem ser chamadas por nome não qualificado, mesmo em contextos não avaliados, quando isso pode ser usado ([over.call.func] p3)," está incorreto. O que realmente diz é

Se a palavra-chave this(9.3.2) está no escopo e se refere à classe T, ou uma classe derivada de T, o argumento do objeto implícito está (*this). Se a palavra this- chave não estiver no escopo ou se referir a outra classe, um objeto inventado do tipo Tse tornará o argumento do objeto implícito. Se a lista de argumentos for aumentada por um objeto inventado e a resolução de sobrecarga selecionar uma das funções de membro não estáticas de T, a chamada será malformada.

Dentro de uma função de membro estático, thispode não aparecer, mas ainda existe.

No entanto, de acordo com os comentários, dentro de uma função de membro estática, a transformação de f()para (*this).f()não seria realizada, e se isso não for realizado, então [expr.call] p1 é violado:

[...] Para uma chamada de função de membro, a expressão postfix deve ser um acesso de membro de classe implícito (9.3.1, 9.4) ou explícito (5.2.5) cujo [...]

pois não haveria acesso de membro. Portanto, mesmo isso não funcionaria.


fonte
Eu acho que [class.mfct.non-static] / 3 diz que _self_fn_1()é "transformado" em (*this)._self_fn_1(). Não tenho certeza se isso o torna ilegal.
dyp
@dyp Diz "é usado em um membro da classe Xem um contexto onde thispode ser usado", então não acho que a transformação seja realizada.
1
Mas então não é um acesso de membro de classe implícito nem explícito ..? [expr.call] / 1 "Para uma chamada de função de membro, a expressão postfix deve ser um acesso de membro de classe implícito ou explícito [...]"
dyp
(Quero dizer, o que acontece quando você tem auto _self_fn_1() -> decltype(*this); auto _self_fn_1() const -> decltype(*this);?)
dyp
@dyp [expr.call] / 1 é um bom ponto, vou ter que dar uma olhada mais de perto. Sobre constsobrecargas, porém: isso não é um problema. 5.1p3 foi modificado especificamente para se aplicar também a funções-membro estáticas, e diz que o tipo de thisé Foo*/ Bar*(sem const), porque não há constna declaração de _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
};

isso não funciona em tipos de modelo, pois self_checknão é chamado, portanto, o static_assertnão é avaliado.

Podemos fazer alguns hacks para fazê-lo funcionar para templates também, mas tem um custo menor de tempo de execução.

#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

um vazio structde tamanho 1 byte é criado em sua classe. Se o seu tipo for instanciado, selfserá testado.

Yakk - Adam Nevraumont
fonte
Isso também não é ruim!
Lightness Races in Orbit de
@LightnessRacesinOrbit agora com templateopções de suporte de classe.
Yakk - Adam Nevraumont
Eu estava pensando exatamente nisso quando estava saindo do trabalho ontem. Você chegou antes de mim :). Eu sugiro declarar self_check () como embutido, para evitar problemas de vinculação (mesmo símbolo Foo :: self_check () encontrado em vários arquivos de objeto).
o suíno de
1
@theswine: 9.3 / 2 é o índice de um parágrafo no padrão C ++, o que garante que as funções membro da classe definidas no corpo da definição da classe já são, implicitamente inline,. Isso significa que você não precisa escrever inlinenada. Portanto, se você tem escrito inlineantes de cada definição de função de membro de classe em toda a sua carreira, pode parar agora;)
Lightness Races in Orbit
2
@LightnessRacesinOrbit Oh, na verdade eu estava. Obrigado, isso vai me poupar alguma digitação no futuro :). Sempre fico surpreso com o quanto não sei sobre C ++.
o suíno de
11

Eu também acho que é impossível, aqui está outra tentativa falha mas interessante IMHO que evita o 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, "" );
}

que falha porque C ++ exige que você se qualifique self_fcom a classe quando quiser pegar seu endereço :(

Daniel Frey
fonte
E o mesmo problema acontece com um int T::*ponteiro regular para uma variável de membro. E int self_var; typedef decltype(&self_var) self_ptrtambém não funciona, é apenas um normal int*.
MSalters de
9

Recentemente descobri que isso *thisé permitido em um inicializador de chave ou igual . Descrito no § 5.1.1 ( do esboço de trabalho n3337 ):

3 [..] Ao contrário da expressão de objeto em outros contextos, *thisnão é necessário ser do tipo completo para fins de acesso de membro de classe (5.2.5) fora do corpo da função de membro. [..]

4 Caso contrário, se um declarador de membro declara um membro de dados não estático (9.2) de uma classe X, a expressão thisé um prvalue do tipo “ponteiro para X” dentro do inicializador opcional de chave ou igual . Não deve aparecer em nenhum outro lugar do membro declarador .

5 A expressão thisnão deve aparecer em nenhum outro contexto. [ Exemplo:

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

- exemplo final ]

Com isso em mente, o seguinte código:

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

passa de Daniel Frey static_assert .

Live example

Comunidade
fonte
Você tem uma variável inútil irritante testembora
MM
@Matt True, mas ainda achei interessante.
1
Isso poderia ter funcionado sem = this, certo? E por que não apenasusing self = Foo*;
user362515
1
Não ganhamos nada aqui com certeza, porque tivemos que declarar testser do tipo, hum Foo *,!
Paul Sanders
4

A menos que o tipo precise ser um tipo de membro da classe envolvente, você pode substituir o uso selfpor decltype(*this). Se você usá-lo em muitos lugares em seu código, poderá definir uma macro da SELFseguinte maneira:

#define SELF decltype(*this)
TAS
fonte
2
E você não pode usar isso fora da classe ou em classes aninhadas
Drax
1
@Drax: Não deveria estar disponível fora da classe.
Ben Voigt de
@BenVoigt Mas é suposto estar disponível em classes aninhadas, o que é IMO o caso de uso mais interessante.
Drax de
1
Acho que não. Não deveria se selfreferir à classe imediatamente envolvente e não à classe externa? Mas não conheço muito bem o php.
Ben Voigt
1
@LightnessRacesinOrbit: Suponho que o código e o erro digam "PHP não tem tipos aninhados"?
Ben Voigt de
1

Forneça minha versão. O melhor é que seu uso é igual ao da classe nativa. No entanto, isso não funciona para classes de modelo.

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); }
};
user1899020
fonte
1

Com base na resposta de hvd, descobri que a única coisa que faltava era remover a referência, é por isso que a verificação std :: is_same falha (b / c o tipo resultante é na verdade uma referência ao tipo). Agora, esta macro sem parâmetros pode fazer todo o trabalho. Exemplo de trabalho abaixo (eu uso o 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
fonte
Ele não compila em outros compiladores além do GCC.
zedu
0

Vou repetir a solução óbvia de "ter que fazer você mesmo". Esta é a versão C ++ 11 sucinta do código, que funciona com classes simples e modelos de classe:

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

Você pode ver isso em ação no ideone . A gênese que levou a esse resultado está abaixo:

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

struct XYZ {
    DECLARE_SELF(XYZ)
};

Isso tem o problema óbvio de copiar e colar o código para uma classe diferente e esquecer de alterar XYZ, como aqui:

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

Minha primeira abordagem não foi muito original - fazer uma função, como esta:

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

É meio demorado, mas, por favor, tenha paciência comigo aqui. Isso tem a vantagem de trabalhar em C ++ 03 sem decltype, já que a __self_check_helperfunção é empregada para deduzir o tipo de this. Além disso, não há static_assert, mas o sizeof()truque é empregado. Você poderia torná-lo muito mais curto para C ++ 0x. Agora, isso não funcionará para modelos. Além disso, há um pequeno problema com a macro que não espera ponto-e-vírgula no final; se compilar com pedante, ela reclamará de um ponto-e-vírgula desnecessário extra (ou você ficará com uma macro de aparência estranha que não termina em ponto-e-vírgula no corpo de XYZe ABC)

Verificar o Typeque é passado DECLARE_SELFnão é uma opção, pois isso apenas verificaria a XYZclasse (que está ok), esquecido ABC(que tem erro). E então isso me atingiu. Uma solução de custo zero sem armazenamento adicional que funciona com modelos:

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 

Isso simplesmente faz asserção estática em um valor enum único (ou pelo menos único no caso de você não escrever todo o seu código em uma única linha), nenhum truque de comparação de tipo é empregado e funciona como assert estático, mesmo em modelos . E como um bônus - o ponto-e-vírgula final agora é necessário :).

Gostaria de agradecer a Yakk por me dar uma boa inspiração. Eu não escreveria isso sem primeiro ver sua resposta.

Testado com VS 2008 e g ++ 4.6.3. Na verdade, com o exemplo XYZe ABC, ele reclama:

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

Agora, se fizermos do ABC um modelo:

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

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

Nós conseguiremos:

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

Apenas a verificação do número da linha foi acionada, pois a verificação da função não foi compilada (como esperado).

Com C ++ 0x (e sem os sublinhados malignos), você precisaria apenas:

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

Acredito que o bit CStaticAssert infelizmente ainda seja necessário, pois produz um tipo, que é digitado no corpo do modelo (suponho que o mesmo não pode ser feito com static_assert). A vantagem dessa abordagem ainda é seu custo zero.

o porco
fonte
Você está essencialmente reimplementando static_assertaqui, não é? Além disso, seu código completo é inválido porque você está usando identificadores ilegais (reservados).
Konrad Rudolph
@KonradRudolph Sim, esse é realmente o caso. Não tenho C ++ 0x no momento, então reimplementei static_assert para fornecer uma resposta completa. Eu digo isso na resposta. É inválido? Você pode apontar como? Compilou bem, estou usando agora.
o suíno de
1
Os identificadores são inválidos porque C ++ reserva tudo com um sublinhado inicial seguido por uma letra maiúscula, bem como dois sublinhados iniciais no escopo global, para o compilador. O código do usuário não deve usá-lo, mas nem todos os compiladores irão sinalizá-lo como um erro.
Konrad Rudolph
@KonradRudolph eu vejo, eu não sabia disso. Tenho muito código que usa isso, nunca tive problemas com ele nem no Linux / Mac / Windows. Mas acho que é bom saber.
o suíno de
0

Não sei tudo sobre esses modelos malucos, que tal algo super simples:

#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

Trabalho concluído, a menos que você não aguente algumas macros. Você pode até usar CLASSNAMEpara declarar seu (s) construtor (es) (e, é claro, destruidor).

Demonstração ao vivo .

Paul Sanders
fonte
1
Tem um efeito bastante pronunciado sobre como a classe pode / deve ser usada
Lightness Races in Orbit
@LightnessRacesinOrbit Como assim? Eu não vejo isso. Eu, refletindo, removi a frase final de minha postagem original. O que eu tinha lá originalmente pode ter levado você a pensar assim.
Paul Sanders