ventajas - ¿Puedo implementar un tipo de miembro auto-autónomo en C++?
vehiculos autonomos historia (11)
C ++ lacks del equivalente de la palabra clave self
de PHP , que se evalúa como el tipo de clase que lo incluye.
Es bastante fácil falsificarlo por clase:
struct Foo
{
typedef Foo self;
};
pero tuve que escribir Foo
nuevamente. Tal vez me equivoque un día y cause un error silencioso.
¿Puedo usar alguna combinación de decltype
y amigos para hacer que este trabajo sea "autónomo"? Intenté lo siguiente, pero this
no 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 voy a preocuparme por el equivalente de static
, que hace lo mismo pero con el enlace tardío).
A menos que el tipo necesite ser el tipo de miembro de la clase decltype(*this)
puede reemplazar el uso de self
con decltype(*this)
. Si lo usa en muchos lugares en su código, puede definir una macro SELF
siguiente manera:
#define SELF decltype(*this)
Así es cómo puedes 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_DERIVED
de la siguiente manera:
class WITH_SELF_DERIVED(Bar,Foo)
{
/* ... */
};
Incluso puede hacer herencia múltiple con tantas clases base como desee (gracias a las plantillas variadic y las macros variadas):
class WITH_SELF(Foo2)
{
/* ... */
};
class WITH_SELF_DERIVED(Bar2,Foo,Foo2)
{
/* ... */
};
He verificado que esto funciona en gcc 4.8 y clang 3.4.
Lo que funciona tanto en GCC como en clang es crear un typedef que se refiera a this
usando this
en el tipo de retorno de una función typedef. Como esta no es la declaración de una función miembro estática, se tolera el uso de this
. A continuación, 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 verificar que this
no se use en la definición de una función miembro estática. Y aquí, de hecho no lo es. A GCC no le importa si this
se usa en un tipo de retorno final independientemente del tipo de función, incluso si se trata de funciones de miembro static
. Sin embargo, lo que el estándar realmente requiere es que this
no se use fuera de la definición de una función de miembro no estática (o inicializador de miembro de datos no estático). Intel lo hace bien y lo rechaza.
Dado que:
-
this
solo se permite en inicializadores de miembros de datos no estáticos y funciones miembro no estáticas ([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 a 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
this
se puede 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 concluyentemente que no hay forma de implementar self
sin incluir de alguna manera, en alguna parte, el nombre del tipo.
Editar : Hay un defecto en mi razonamiento anterior. "Las funciones miembro no estáticas solo se pueden llamar por 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 claseT
, o a una clase derivada deT
, entonces el argumento del objeto implícito es(*this)
. Si la palabra clavethis
no está en el alcance o se refiere a otra clase, entonces un objeto artificial de tipoT
convierte en el argumento del objeto implícito. Si la lista de argumentos se aumenta mediante un objeto artificial y la resolución de sobrecarga selecciona una de las funciones miembro no estáticas deT
, la llamada se forma mal.
Dentro de una función miembro estática, this
puede no aparecer, pero todavía existe.
Sin embargo, según los comentarios, dentro de una función de miembro estático, la transformación de f()
a (*this).f()
no se realizaría, y si no se realiza, entonces [expr.call] p1 se infringe:
[...] Para una llamada de función miembro, la expresión postfija será un acceso implícito (9.3.1, 9.4) o miembro de clase explícito (5.2.5) [...]
ya que no habría acceso de miembros. Entonces incluso eso no funcionaría.
No tengo evidencia positiva, pero creo que es imposible. Lo siguiente falla, por el mismo motivo que tu 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_());
};
Básicamente, lo que esto demuestra es que el alcance en el que queremos declarar nuestro typedef simplemente no tiene acceso (sea directo o indirecto) a this
, y no hay otra forma (independiente del compilador) de llegar al tipo o nombre de la clase.
Proporcionar 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); }
};
Puede usar una macro en lugar de una declaración de clase normal, eso lo hará por usted.
#define CLASS_WITH_SELF(X) class X { typedef X self;
Y luego usar como
CLASS_WITH_SELF(Foo)
};
#define END_CLASS };
probablemente ayudaría a la legibilidad.
También puedes tomar @ Paranaix''s Self
y usarlo (empieza a ser realmente hackish)
#define WITH_SELF(X) X : public Self<X>
class WITH_SELF(Foo) {
};
Recientemente descubrí que *this
está 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,
*this
no es obligatorio que sea de tipo completo para el acceso de los miembros de la clase (5.2.5) fuera del cuerpo de la función del miembro. [..]4 De lo contrario, si un miembro declarador declara un miembro de datos no estático (9.2) de una clase X, la expresión es un valor pr de tipo "puntero a X" dentro del inicializador de llave o igual opcional. No aparecerá en ninguna otra parte del miembro declarador .
5 La expresión
this
no debe 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 static_assert
Daniel Frey .
Repetiré la solución obvia de "tener que hacerlo tú mismo". Esta es la versión sucinta del código de C ++ 11, que funciona con clases simples y plantillas de clase:
#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, está a continuación:
#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 olvidarse de cambiar XYZ, como aquí:
struct ABC {
DECLARE_SELF(XYZ) // !!
};
Mi primer enfoque no fue muy original: crear 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 aquí. Esto tiene la ventaja de trabajar en C ++ 03 sin decltype
, ya que la función __self_check_helper
se emplea para deducir el tipo de this
. Además, no hay static_assert
, pero en su lugar se emplea el truco sizeof()
. Podría hacerlo mucho más corto para C ++ 0x. Ahora esto no funcionará para las plantillas. Además, hay un problema menor con la macro que no espera punto y coma al final, si se compila con pedantic, se quejará de un punto y coma adicional innecesario (o se quedará con una macro de aspecto extraño que no termina en punto y coma en el cuerpo de XYZ
y ABC
).
Controlar el Type
que se pasa a DECLARE_SELF
no es una opción, ya que eso solo verificaría la clase XYZ
(lo cual está bien), ajeno a ABC
(que tiene error). Y entonces me di cuenta. Una solución de cero costo de almacenamiento 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 aserción estática en un valor de enum único (o al menos único en caso de que no escriba todo su código en una sola línea), no se emplea trucos de comparación de tipos, y funciona como una afirmación estática, incluso en plantillas . Y como extra, 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 XYZ
y 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 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 función no se compiló (como se esperaba).
Con C ++ 0x (y sin los guiones bajos del mal), necesitarías simplemente:
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 todavía se requiere el bit CStaticAssert ya que produce un tipo, que se escribe en el cuerpo de la plantilla (supongo que no se puede hacer lo mismo con static_assert
). La ventaja de este enfoque sigue siendo su costo cero.
También creo que es imposible, aquí hay otro intento fallido pero en mi humilde opinión, que evita el acceso a this
:
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 califique self_f
con la clase cuando quiere tomar su dirección :(
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 T
realidad 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 un static_assert
dentro de una función miembro es probablemente la única manera de verificar, ya que los tipos pasados a std::is_base_of
deben estar completos.
#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 los tipos de plantilla, ya que no se llama a static_assert
, por lo que static_assert
no se evalúa.
Podemos hacer algunos hacks para que también funcione para template
, pero tiene un costo de tiempo de ejecución menor.
#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
una struct
vacía de tamaño 1 byte se crea en su clase. Si su tipo es instanciado, el self
se prueba en contra.