2009-10-04 8 views
9

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:

BOOST_MPL_ASSERT_MSG

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.

+0

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. –

+0

"' 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

+1

sbi: Seguí y etiqueté todos a "metaprogramación" – xian

Respuesta

4

Lo que intenta hacer es comprobar explícitamente si los tipos pasados ​​como argumentos de plantilla proporcionan los conceptos necesarios. A falta de la función de concepto, que se expulsó de C++ 0X (y por lo tanto es uno de los principales culpables de que se convierta en C++ 1X) es ciertamente difícil hacer una correcta verificación del concepto. Desde los años 90, ha habido varios intentos de crear bibliotecas de verificación de conceptos sin soporte de idiomas, pero, básicamente, todo lo que han logrado es mostrar que, para hacerlo bien, los conceptos deben convertirse en una característica del lenguaje central, en lugar de que una función solo de biblioteca.

No encuentro sus ideas de derivar en lugar de typedef y el uso de enable_if es muy atractivo. Como ha dicho usted mismo, a menudo oscurece el código real solo por el bien de los mejores mensajes de error del compilador.

Encuentro que la afirmación estática es mucho mejor. No es necesario cambiar el código real, todos estamos acostumbrados a tener comprobaciones de aserción en algoritmos y aprendimos a omitirlas mentalmente si queremos entender los algoritmos reales, puede producir mejores mensajes de error y se transferirá a C++ 1X mejor, que va a tener un static_assert (completamente con los mensajes de error proporcionados por el diseñador de clase) integrados en el idioma. (Sospecho que BOOST_STATIC_ASSERT simplemente use el static_assert incorporado si está disponible.)

+0

Estoy de acuerdo que parece estúpido tapar el código para obtener mejores errores ... –

+0

@ Matthieu: Realmente no estoy de acuerdo con esa afirmación: le ocupa el código de tiempo de ejecución para obtener mejores mensajes de error en tiempo de ejecución, después de todo. Pero parece que 'static_assert' logra aproximadamente lo mismo con menos desorden. – sbi

+0

Sí, eso es lo que quise decir ... bueno, tengo algunos problemas con el inglés: \ ... solo quise decir que usar enable_if es una manera muy verídica de probar (en comparación con un static_assert), creo que hay probablemente en algunos casos en los que esta es la única forma posible; de ​​lo contrario, ¿por qué los tipos de Boost crearían una bestia así? –