Aviso: La pregunta es completamente diferente de Inheritance instead of typedef y no pude encontrar ninguna pregunta similar en lo que vatypedef vs herencia pública en C++ Proyecto Insight
me gusta jugar con C++ plantilla de meta-programación (en el hogar sobre todo, a veces lo presento a la ligera en el trabajo, pero no quiero que el programa se vuelva legible para cualquiera que no se haya molestado en aprender sobre él), sin embargo los errores del compilador me han afectado bastante cada vez que algo sale mal.
El problema es que la metaprogramación de plantillas C++ se basa en plantillas y, por lo tanto, cada vez que obtienes un error de compilación dentro de una estructura de plantillas anidadas, tienes que abrir un camino en un mensaje de error de 10 líneas . Incluso he tomado el hábito de copiar/pegar el mensaje en un editor de texto y luego sangrar el mensaje para obtener algo de estructura hasta que tenga una idea de lo que realmente está sucediendo, lo que agrega algo de trabajo al seguimiento del error en sí.
Hasta donde yo sé, el problema se debe principalmente al compilador y a la salida de typedefs (hay otros problemas como la profundidad de anidación, pero en realidad no es el fallo del compilador). Se anuncian funciones geniales como plantillas variadic o deducción de tipo (automático) para el próximo C++ 0x, pero realmente me gustaría tener mejores mensajes de error para arrancar. Puede resultar doloroso utilizar la metaprogramación de plantillas, y me pregunto qué pasará cuando más personas entren en ellas.
He reemplazado algunos de los typedefs en mi código, y uso la herencia en su lugar.
typedef partition<AnyType> MyArg;
struct MyArg2: partition<AnyType> {};
No hay mucho más caracteres para escribir, y esto no es menos legible en mi opinión. De hecho, podría ser incluso más legible, ya que garantiza que el nuevo tipo declarado aparece cerca del margen izquierdo, en lugar de estar en un desplazamiento indeterminado a la derecha.
Sin embargo, esto implica otro problema. Con el fin de asegurarse de que no hice nada estúpido, a menudo escribía mis funciones plantillas/clases de este modo:
template <class T> T& get(partition<T>&);
De esta manera yo estaba seguro de que sólo se puede invocar para un objeto adecuado.
Especialmente cuando se sobrecargan operadores como operador +, se necesita alguna manera de reducir el alcance de sus operadores, o se corre el riesgo de que se invoque para int, por ejemplo.
Sin embargo, si esto funciona con un tipo typedef'ed, ya que es solo un alias. Seguro que no funciona con la herencia ...
Para las funciones, uno puede simplemente utilizar el CRTP
template <class Derived, class T> partition;
template <class Derived, class T> T& get(partition<Derived,T>&);
Esto permite conocer el tipo 'real' que se utiliza para invocar el método antes de que el compilador utilizado la herencia pública. Se debe tener en cuenta que esto reduce las posibilidades de que se invoque esta función particular, ya que el compilador debe realizar una transformación, pero hasta ahora no he notado ningún problema.
Otra solución a este problema es agregar una propiedad 'etiqueta' a mis tipos, para distinguirlos entre sí, y luego contar en SFINAE.
struct partition_tag {};
template <class T> struct partition { typedef partition_tag tag; ... };
template <class T>
typename boost::enable_if<
boost::same_type<
typename T::tag,
partition_tag
>,
T&
>::type
get(T&)
{
...
}
Se requiere un poco más a escribir, sin embargo, sobre todo si se declara y define la función/método en diferentes lugares (y si no me molesta mi interfaz es muy pronto confusa). Sin embargo, cuando se trata de clases, ya que no se realiza ninguna transformación de tipos, que se hace algo más complicado:
template <class T>
class MyClass { /* stuff */ };
// Use of boost::enable_if
template <class T, class Enable = void>
class MyClass { /* empty */ };
template <class T>
class MyClass <
T,
boost::enable_if<
boost::same_type<
typename T::tag,
partition_tag
>
>
>
{
/* useful stuff here */
};
// OR use of the static assert
template <class T>
class MyClass
{
BOOST_STATIC_ASSERT((/*this comparison of tags...*/));
};
que tienden a utilizar más el 'afirman estática' que el 'enable_if', creo que es mucho más legible cuando regrese después de un tiempo.
Bueno, básicamente aún no me he decidido y todavía estoy experimentando entre las diferentes técnicas expuestas aquí.
¿Utiliza typedefs o herencia? ¿Cómo se restringe el alcance de sus métodos/funciones o se controla el tipo de argumentos que se les proporciona (y para las clases)?
Y, por supuesto, me gustaría tener más preferencias personales si es posible. Si hay una buena razón para usar una técnica en particular, ¡preferiría saberlo!
EDIT:
estaba navegando stackoverflow y acabo de encontrar esta Perl desde Boost.MPL me había olvidado por completo:
La idea es que le dan la macro 3 argumentos:
- La condición para comprobar
- un mensaje (identificador de C++) que debe ser utilizado para la visualización del mensaje de error
- la lista de tipos implicados (como una tupla)
Puede ayudar considerablemente tanto en la documentación del código auto y una mejor salida de error.
Re: ¿Por qué hay dos etiquetas?Porque alguien tecleó lo suficientemente rápido y no le importó verificar las etiquetas antiguas. Y luego había dos. Y la gente eligió lo que pensaban que estaba escrito correctamente. Y hasta donde tengo conocimiento, una vez que se crea una etiqueta en SO, incluso si está vacía, entonces aún aparece en la lista de etiquetas disponibles. –
"' meta-programming' "tiene 33 entradas, y" 'metaprogramming'" tiene 134 entradas. Encontraría el anterior mejor, pero si la mayoría ... De todos modos, ¿cuál es la política de SO con respecto a pasar por 33 viejas preguntas y volver a etiquetarlas? –
sbi
sbi: Seguí y etiqueté todos a "metaprogramación" – xian