¿Puedo implementar un tipo de miembro autónomo en C ++?

101

C ++ carece del equivalente de la selfpalabra clave de PHP , que se evalúa según el tipo de la clase adjunta.

Es bastante fácil fingirlo por clase:

struct Foo
{
   typedef Foo self;
};

pero tuve que escribir de Foonuevo. Tal vez algún día me equivoque y cause un error silencioso.

¿Puedo usar alguna combinación de decltypey amigos para hacer que esto funcione de manera "autónoma"? Ya intenté lo siguiente, pero thisno es válido en ese lugar:

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

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

(No me voy a preocupar por el equivalente de static, que hace lo mismo pero con enlace tardío).

Carreras de ligereza en órbita
fuente
9
this_tprobablemente estaría más alineado con los nombres normales de C ++.
Bartek Banachewicz
3
@BartekBanachewicz: o this_type
PlasmaHH
10
@Praetorian, no puedo recordar si fue una propuesta o no, pero alguien sugirió auto()y ~auto()para ctors / dtors. Interesante por decir lo menos. Si se usa para ese propósito, tal vez typedef auto self;, pero eso me parece un poco esquemático.
chris
11
Honestamente, si fuera a sugerir una sintaxis para hacer esto posible, probablemente sería decltype(class), tal vez con un decltype(struct)equivalente. Eso es mucho más claro que solo autoen un contexto específico y no veo ningún problema con que encaje en el lenguaje basado en decltype(auto).
chris
11
Como desea evitar errores, puede configurar una función de miembro ficticia con static_assert, como void _check() { static_assert(std::is_same<self&, decltype(*this)>::value, "Correct your self type"); }No funciona con plantillas de clase, aunque ...
milleniumbug

Respuestas:

38

Así es como puede hacerlo sin repetir el 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;
    }
};

Si desea derivar de Foo, debe usar la macro WITH_SELF_DERIVEDde la siguiente manera:

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

Incluso puede hacer herencia múltiple con tantas clases base como desee (gracias a plantillas variadas y macros variadic):

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

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

He verificado que esto funciona en gcc 4.8 y clang 3.4.

Ralph Tandetzky
fuente
18
Supongo que la respuesta es "no, ¡pero Ralph puede!" ;)
Lightness Races in Orbit
3
¿Cómo es esto de alguna manera superior a simplemente poner el typedef allí? Y Dios, ¿por qué necesitarías el typedef? ¿Por qué?
Miles Rout
7
@MilesRout Esta es una pregunta sobre la pregunta, no la respuesta. En muchos casos en el desarrollo de software (y especialmente en el mantenimiento), es útil evitar redundancias en el código, de modo que cambiar algo en un lugar no requiera que cambie el código en otro lugar. Ese es el punto de autoy decltypeo en este caso de self.
Ralph Tandetzky
1
template<typename T>class Self{protected: typedef T self;}; class WITH_SELF(Foo) : public Bar, private Baz {};habría sido más simple y permitiría un control más preciso sobre la herencia, ¿hay alguna razón en contra?
Aconcagua
@mmmmmmmm, si no ha aprendido a apreciar profundamente el principio de "No se repita", es probable que aún no haya codificado lo suficiente / en serio. Este "desorden" (lejos de serlo, en realidad) es una solución bastante elegante en el contexto de hablar sobre una característica del lenguaje poco elegante (o un error, incluso una deficiencia según algunas medidas estrictas).
Sz.
38

Una posible solución alternativa (ya que todavía tiene que escribir el tipo una vez):

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

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

Para una versión más segura podríamos asegurar que en Trealidad se deriva de Self<T>:

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

Tenga en cuenta que una static_assertfunción miembro dentro de una es probablemente la única forma de verificar, ya que los tipos pasados std::is_base_ofdeben estar completos.

Sebastián Hoffmann
fuente
4
No es necesario typenameen el typedef. Y dado que esto no reduce el número de despidos, no creo que sea una alternativa viable.
Konrad Rudolph
Tiene exactamente el mismo problema de repetir el Foonombre.
Bartek Banachewicz
6
Sin embargo, es ligeramente mejor que el enfoque original, ya que la repetición es muy cercana. No es una solución a la pregunta, sino +1 para un intento digno de una solución alternativa en el mejor de los casos.
Lightness Races in Orbit
4
Usé esa solución un par de veces, y tiene una cosa MALA: cuando más tarde se deriva de Foo, debe: (1) propagar la T hacia arriba al descendiente de la hoja, o (2) recordar heredar de SelfT muchas veces , o (3) aceptar que todos los niños creen que es la Base ... utilizable, pero poco agradable.
quetzalcoatl
@quetzalcoatl: Ya que estoy tratando de replicar en selflugar de static, no hay problema.
Lightness Races in Orbit
33

Puede usar una macro en lugar de una declaración de clase normal, que lo hará por usted.

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

Y luego usa like

CLASS_WITH_SELF(Foo) 
};

#define END_CLASS }; probablemente ayudaría a la legibilidad.


También puede tomar @ Paranaix Selfy usarlo (comienza a ponerse realmente pirateado)

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

class WITH_SELF(Foo) {
};
Bartek Banachewicz
fuente
18
EWWWW END_CLASS. Eso es totalmente innecesario.
Puppy
31
@DeadMG Creo que a algunas personas les puede gustar una mayor coherencia; después de todo, el primer uso de macro no termina con {, por lo que }está "colgado", lo que probablemente a los editores de texto tampoco les gustará.
Bartek Banachewicz
6
Buena idea, pero aunque no me opongo fundamentalmente a las macros, solo aceptaría su uso aquí si imitara el alcance de C ++, es decir, si fuera utilizable como CLASS_WITH_SELF(foo) { … };- y creo que eso es imposible de lograr.
Konrad Rudolph
2
@KonradRudolph También agregué una forma de hacer eso. No es que me guste, solo en aras de la integridad
Bartek Banachewicz
1
Sin embargo, hay algunos problemas con ese enfoque. El primero no le permite hacer que la clase herede fácilmente (a menos que use otro (s) parámetro (s) de macro), y el segundo tiene todos los problemas de heredar de él Self.
Bartek Banachewicz
31

No tengo pruebas positivas, pero creo que es imposible. Lo siguiente falla, por la misma razón que su intento, y creo que eso es lo más lejos que podemos llegar:

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

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

Esencialmente, lo que esto demuestra es que el alcance en el que queremos declarar nuestra typedef simplemente no tiene acceso (ya sea directo o indirecto) thisy no hay otra forma (independiente del compilador) de llegar al tipo o nombre de la clase.

Konrad Rudolph
fuente
4
¿Será esto posiblemente con la deducción del tipo de retorno de C ++ 1y?
dyp
4
@dyp Para el propósito de mi respuesta, eso no cambiará nada. El error aquí no está en el tipo de retorno final, está en la invocación.
Konrad Rudolph
1
@quetzalcoatl: Las entrañas de decltypees un contexto no evaluado, por lo que invocar la función del miembro no es el problema (eso no se intentará)
Lightness Races in Orbit
1
@TomKnapen Pruébelo con sonido metálico y fallará. El hecho de que sea aceptado por GCC es un error, hasta donde yo sé.
4
FWIW, struct S { int i; typedef decltype(i) Int; };funciona aunque isea ​​un miembro de datos no estático. Funciona porque decltypetiene una excepción especial en la que un nombre simple no se evalúa como expresión. Pero no puedo pensar en ninguna forma de utilizar esta posibilidad de una manera que responda a la pregunta.
21

Lo que funciona tanto en GCC como en clang es crear una typedef que haga referencia al thisuso thisen el tipo de retorno de una función typedef. Dado que esta no es la declaración de una función miembro estática, thisse tolera el uso de . Luego puede usar ese 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();
};

Desafortunadamente, una lectura estricta del estándar dice que incluso esto no es válido. Lo que hace clang es comprobar que thisno se utiliza en la definición de una función miembro estática. Y aquí, de hecho, no lo es. A GCC no le importa si thisse usa en un tipo de retorno al final independientemente del tipo de función, lo permite incluso para staticfunciones miembro. Sin embargo, lo que el estándar realmente requiere es que thisno se use fuera de la definición de función miembro no estática (o inicializador de miembro de datos no estático). Intel lo hace bien y lo rechaza.

Dado que:

  • this solo está permitido en inicializadores de miembros de datos no estáticos y funciones de miembros no estáticos ([expr.prim.general] p5),
  • los miembros de datos no estáticos no pueden tener su tipo deducido del inicializador ([dcl.spec.auto] p5),
  • Las funciones miembro no estáticas solo pueden ser referidas por un nombre no calificado en el contexto de una llamada de función ([expr.ref] p4)
  • Las funciones miembro no estáticas solo se pueden llamar por nombre no calificado, incluso en contextos no evaluados, cuando thisse pueden usar ([over.call.func] p3),
  • una referencia a una función miembro no estática por nombre calificado o acceso de miembro requiere una referencia al tipo que se está definiendo

Creo que puedo decir de manera concluyente que no hay forma de implementarlo selfsin incluir de alguna manera, en algún lugar, el nombre del tipo.

Editar : Hay una falla en mi razonamiento anterior. "Las funciones miembro no estáticas solo se pueden llamar por un nombre no calificado, incluso en contextos no evaluados, cuando esto se puede usar ([over.call.func] p3)", es incorrecto. Lo que realmente dice es

Si la palabra clave this(9.3.2) está dentro del alcance y se refiere a la clase T, o una clase derivada de T, entonces el argumento del objeto implícito es (*this). Si la palabra clave thisno está dentro del alcance o se refiere a otra clase, entonces un objeto de tipo artificial se Tconvierte en el argumento del objeto implícito. Si la lista de argumentos se aumenta con un objeto artificial y la resolución de sobrecarga selecciona una de las funciones miembro no estáticas de T, la llamada está mal formada.

Dentro de una función miembro estática, thispuede que no aparezca, pero aún existe.

Sin embargo, según los comentarios, dentro de una función miembro estática, la transformación de f()a (*this).f()no se realizaría, y si no se realiza, se infringe [expr.call] p1:

[...] Para una llamada de función miembro, la expresión de sufijo será un acceso implícito (9.3.1, 9.4) o explícito a un miembro de clase (5.2.5) cuyo [...]

ya que no habría acceso de miembros. Así que incluso eso no funcionaría.


fuente
Creo que [class.mfct.non-static] / 3 dice que _self_fn_1()se "transforma" en (*this)._self_fn_1(). Sin embargo, no estoy seguro de si eso lo hace ilegal.
dyp
@dyp Dice "se usa en un miembro de la clase Xen un contexto donde thisse puede usar", por lo que no creo que se realice una transformación.
1
Pero entonces no es un acceso de miembro de clase implícito ni explícito ..? [expr.call] / 1 "Para una llamada de función miembro, la expresión de sufijo será un acceso de miembro de clase implícito o explícito [...]"
dyp
(Quiero decir, ¿qué pasa cuando tienes auto _self_fn_1() -> decltype(*this); auto _self_fn_1() const -> decltype(*this);?)
dyp
@dyp [expr.call] / 1 es un buen punto, tendré que mirar más de cerca. Sin constembargo, sobre las sobrecargas: eso no es un problema. 5.1p3 se ha modificado específicamente para aplicarse también a funciones miembro estáticas, y dice que el tipo de thises Foo*/ Bar*(sin const), porque no hay consten la declaración 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
};

esto no funciona en tipos de plantilla, ya self_checkque no se llama, por lo static_assertque no se evalúa.

Podemos hacer algunos trucos para que funcione también para templates, pero tiene un costo menor de tiempo de ejecución.

#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

structse crea un vacío de 1 byte de tamaño en su clase. Si se crea una instancia de su tipo, selfse prueba con.

Yakk - Adam Nevraumont
fuente
¡Eso tampoco está mal!
Lightness Races in Orbit
@LightnessRacesinOrbit ahora con templateopciones de soporte de clase.
Yakk - Adam Nevraumont
Estaba pensando exactamente en esto cuando salía del trabajo ayer. Me ganaste :). Sugeriría declarar self_check () como en línea, para evitar problemas de vinculación (el mismo símbolo Foo :: self_check () se encuentra en varios archivos de objeto).
el cerdo
1
@theswine: 9,3 / 2 es el índice de un párrafo en la norma C ++, que garantiza que las funciones de miembro de clase definida en el cuerpo de la definición de clase ya son, implícitamente, inline. Eso significa que no necesitas escribir inlinenada. Entonces, si ha estado escribiendo inlinefrente a cada definición de función de miembro de clase durante toda su carrera, puede detenerse ahora;)
Lightness Races in Orbit
2
@LightnessRacesinOrbit Oh, de hecho lo estaba. Gracias, eso me ahorrará algo de escribir en el futuro :). Siempre me sorprende lo mucho que no sé sobre C ++.
el cerdo
11

También creo que es imposible, aquí hay otro intento fallido, pero en mi humilde opinión, interesante que evita el thisacceso:

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 falla porque C ++ requiere que califiques self_fcon la clase cuando quieras tomar su dirección :(

Daniel Frey
fuente
Y el mismo problema ocurre con un int T::*puntero regular a una variable miembro. Y int self_var; typedef decltype(&self_var) self_ptrtampoco funciona, es algo normal int*.
MSalters
9

Recientemente descubrí que *thisestá permitido en un inicializador de llave o igual . Descrito en § 5.1.1 ( del borrador de trabajo n3337 ):

3 [..] A diferencia de la expresión de objeto en otros contextos, *thisno se requiere que sea de tipo completo para propósitos de acceso a miembros de clase (5.2.5) fuera del cuerpo de la función miembro. [..]

4 De lo contrario, si un declarador de miembros declara un miembro de datos no estático (9.2) de una clase X, la expresión thises un valor pr de tipo “puntero a X” dentro del inicializador de llaves o igual opcional . No aparecerá en ninguna otra parte del miembro-declarador .

5 La expresión thisno aparecerá en ningún otro contexto. [ Ejemplo:

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

- ejemplo final ]

Con eso en mente, el siguiente 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, "" );
}

pasa el de Daniel Frey static_assert .

Live example

Comunidad
fuente
Sin testembargo
MM
@ Matt True, pero aun así lo encontré interesante.
1
Esto podría haber funcionado sin = this, ¿verdad? Y por qué no solousing self = Foo*;
user362515
1
Nosotros no ganar nada aquí sin duda, porque hemos tenido que declarar testa ser de tipo, um, Foo *!
Paul Sanders
4

A menos que el tipo deba ser un tipo de miembro de la clase adjunta, puede reemplazar el uso de selfcon decltype(*this). Si lo usa en muchos lugares de su código, puede definir una macro de la SELFsiguiente manera:

#define SELF decltype(*this)
TAS
fuente
2
Y no puede usar eso fuera de la clase o en clases anidadas
Drax
1
@Drax: No se supone que esté disponible fuera de la clase.
Ben Voigt
@BenVoigt Pero se supone que está disponible en clases anidadas, que es en mi opinión el caso de uso más interesante.
Drax
1
No lo creo. ¿No debería selfreferirse a la clase inmediatamente circundante y no a la clase exterior? Pero no sé php tan bien.
Ben Voigt
1
@LightnessRacesinOrbit: ¿Supongo que el código y el error dicen "PHP no tiene tipos anidados"?
Ben Voigt
1

Proporcione mi versión. Lo mejor es que su uso es el mismo que el de la clase nativa. Sin embargo, no funciona para clases de plantilla.

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); }
};
usuario1899020
fuente
1

Sobre la base de la respuesta de hvd, descubrí que lo único que faltaba era eliminar la referencia, por eso falla la verificación std :: is_same (b / c, el tipo resultante es en realidad una referencia al tipo). Ahora, esta macro sin parámetros puede hacer todo el trabajo. Ejemplo de trabajo a continuación (uso 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
fuente
No se compila en otros compiladores que no sean GCC.
zedu
0

Repetiré la solución obvia de "tener que hacerlo tú mismo". Esta es la versión sucinta C ++ 11 del código, que funciona tanto con clases simples como con plantillas de clases:

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

Puedes verlo en acción en ideone . La génesis que conduce a este resultado es la siguiente:

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

struct XYZ {
    DECLARE_SELF(XYZ)
};

Esto tiene el problema obvio de copiar y pegar el código en una clase diferente y olvidar cambiar XYZ, como aquí:

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

Mi primer enfoque no fue muy original: hacer una función 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

Es un poco largo, pero por favor tengan paciencia conmigo. Esto tiene la ventaja de trabajar en C ++ 03 sin decltype, ya que la __self_check_helperfunción se emplea para deducir el tipo de this. Además, no hay static_assert, pero en su sizeof()lugar se emplea el truco. Podría hacerlo mucho más corto para C ++ 0x. Ahora bien, esto no funcionará para las plantillas. Además, hay un problema menor con la macro que no espera el punto y coma al final, si se compila con pedante, se quejará de un punto y coma innecesario adicional (o se quedará con una macro de aspecto extraño que no termina en punto y coma en el cuerpo de XYZy ABC).

Hacer una verificación en el Typeque se pasa DECLARE_SELFno es una opción, ya que eso solo verificaría la XYZclase (que está bien), ajena a ABC(que tiene un error). Y entonces me di cuenta. Una solución de almacenamiento sin costo adicional que funciona con plantillas:

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 

Esto simplemente hace una aserción estática en un valor de enumeración único (o al menos único en caso de que no escriba todo su código en una sola línea), no se emplea ningún truco de comparación de tipos y funciona como aserción estática, incluso en plantillas . Y como beneficio adicional, ahora se requiere el punto y coma final :).

Me gustaría agradecer a Yakk por darme una buena inspiración. No escribiría esto sin antes ver su respuesta.

Probado con VS 2008 y g ++ 4.6.3. De hecho, con el ejemplo XYZy ABC, se queja:

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

Ahora, si hacemos de ABC una plantilla:

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

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

Obtendremos:

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

Solo se activó la verificación del número de línea, ya que la verificación de la función no se compiló (como se esperaba).

Con C ++ 0x (y sin los guiones bajos malvados), solo necesitaría:

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

Creo que, lamentablemente, el bit CStaticAssert todavía es necesario ya que produce un tipo, que se define en el cuerpo de la plantilla (supongo que no se puede hacer lo mismo static_assert). La ventaja de este enfoque sigue siendo su coste cero.

los cerdos
fuente
Básicamente, estás reimplementando static_assertaquí, ¿no? Además, su código completo no es válido porque está utilizando identificadores ilegales (reservados).
Konrad Rudolph
@KonradRudolph Sí, ese es el caso. No tengo C ++ 0x en ese momento, así que volví a implementar static_assert para proporcionar una respuesta completa. Digo eso en la respuesta. ¿Es inválido? ¿Puedes señalar cómo? Se compiló bien, lo estoy usando ahora mismo.
el cerdo
1
Los identificadores no son válidos porque C ++ reserva todo con un subrayado inicial seguido de una letra mayúscula, así como dos subrayados iniciales en el ámbito global, para el compilador. El código de usuario no debe usarlo, pero no todos los compiladores lo marcarán como un error.
Konrad Rudolph
@KonradRudolph Ya veo, no sabía eso. Tengo mucho código que usa eso, nunca tuve problemas con él ni en Linux / Mac / Windows. Pero supongo que es bueno saberlo.
el cerdo
0

No sé todo sobre estas plantillas extravagantes, ¿qué tal algo súper simple?

#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

Trabajo hecho, a menos que no puedas soportar un par de macros. Incluso puede usar CLASSNAMEpara declarar su (s) constructor (es) (y, por supuesto, destructor).

Demo en vivo .

Paul Sanders
fuente
1
Tiene un efecto bastante pronunciado sobre cómo se puede / debe usar la clase
Lightness Races in Orbit
@LightnessRacesinOrbit ¿Cómo es eso? Yo no lo veo Después de reflexionar, quité la oración final de mi publicación original. Lo que tenía allí originalmente podría haberte llevado a pensar esto.
Paul Sanders