2011-08-17 11 views

Respuesta

12

Un típico 'truco' para tender un puente en tiempo de compilación y tiempo de ejecución cuando se trata de las plantillas está visitando un tipo de variante. Eso es lo que hace la Biblioteca de Imágenes Genéricas (disponible como Boost.GIL o independiente), por ejemplo. Por lo general toma la forma de:

typedef boost::variant<T, U, V> variant_type; 
variant_type variant = /* type is picked at runtime */ 
boost::apply_visitor(visitor(), variant); 

donde visitor es un funtor polimórfico que simplemente remite a la plantilla:

struct visitor: boost::static_visitor<> { 
    template<typename T> 
    void 
    operator()(T const& t) const 
    { foo(t); } // the real work is in template<typename T> void foo(T const&); 
}; 

Esto tiene el diseño agradable que la lista de tipos que la plantilla se/puede ser instanciado con (aquí, el sinónimo de tipo variant_type) no está acoplado al resto del código. Las metafunciones como boost::make_variant_over también permiten cálculos sobre la lista de tipos para usar.

Dado que esta técnica no está disponible para los parámetros que no son de tipo, es necesario 'desenrollar' las visitas a mano, lo que desafortunadamente significa que el código no es tan legible/mantenible.

void 
bar(int i) { 
    switch(i) { 
     case 0: A<0>::f(); break; 
     case 1: A<1>::f(); break; 
     case 2: A<2>::f(); break; 

     default: 
      // handle 
    } 
} 

La manera habitual de hacer frente a la repetición en el interruptor anterior es que (ab) usar el preprocesador. Un (no probado) ejemplo usando Boost.Preprocessor:

#ifndef LIMIT 
#define LIMIT 20 // 'reasonable' default if nothing is supplied at build time 
#endif 
#define PASTE(rep, n, _) case n: A<n>::f(); break; 

void 
bar(int i) { 
    switch(i) { 
     BOOST_PP_REPEAT(LIMIT, PASTE, _) 

     default: 
      // handle 
    } 
} 

#undef PASTE 
#undef LIMIT 

Mejor encontrar buenos nombres, auto-documentado para LIMIT (no estaría de más para PASTE tampoco), y limitar la anterior generación de código a un solo sitio.


construcción de la solución de David y sus comentarios:

template<int... Indices> 
struct indices { 
    typedef indices<Indices..., sizeof...(Indices)> next; 
}; 

template<int N> 
struct build_indices { 
    typedef typename build_indices<N - 1>::type::next type; 
}; 

template<> 
struct build_indices<0> { 
    typedef indices<> type; 
}; 

template<int... Indices> 
void 
bar(int i, indices<Indices...>) 
{ 
    static void (*lookup[])() = { &A<Indices>::f... }; 
    lookup[i](); 
} 

entonces para llamar bar: bar(i, typename build_indices<N>::type()) donde N sería su constante de tiempo constante, sizeof...(something). Se puede añadir una capa para ocultar la 'fealdad' de esa llamada:

template<int N> 
void 
bar(int i) 
{ bar(i, typename build_indices<N>::type()); } 

que se llama como bar<N>(i).

+0

¿Puedo hacer que el compilador lo desenrolle si sé (en tiempo de compilación) cuántos casos hay? – Predrag

+0

Lo siento por ser doloroso ... pero mi conocimiento del número de casos proviene del operador 'size ...()'. Me temo que el preprocesador no ayudará en ese caso. ¿Puedes pensar en algo que pueda ayudarme con esa restricción? – Predrag

+0

@Predrag Elija un límite superior lo suficientemente grande y con el que se sienta cómodo. –

1

NO
plantillas de implementar compilar polimorfismo tiempo no correr el polimorfismo tiempo.

4

No, las plantillas son una característica de tiempo de compilación, y i no se conoce en tiempo de compilación, por lo que es imposible. A<I>::foo() se debe adaptar a algo como A::foo(i).

+0

Estoy enterado de eso. Vago si tal vez hay alguna solución. – Predrag

+1

La solución consiste en hacer que 'i' se conozca en tiempo de compilación (como la solución de @iamilind) o hacer que' A :: foo' no requiera un argumento en tiempo de compilación (mi solución). – tenfour

+4

Hay una tercera forma (si hay un número limitado de opciones): crear una tabla de búsqueda instanciando todas las funciones en tiempo de compilación y luego enviarlas a una de ellas en tiempo de ejecución. Lo he usado antes y es doloroso, pero factible. –

1

El argumento de la plantilla debe conocerse en tiempo de compilación. Entonces no hay forma de que el compilador pase A<i>::foo().

Si desea evitar entonces usted tiene que hacer también una bar()template:

template<int i> 
void bar() { 
    A<i>::f(); // ok 
} 

Para ello, es necesario conocer argumento para bar() en tiempo de compilación.

8

Dependiendo de lo que quieras hacer exactamente (es decir, ¿hay un número pequeño de instancias limitadas que quieres usar?) Puedes crear una tabla de búsqueda y luego usarla dinámicamente. Para un enfoque totalmente manual, con opciones 0, 1, 2 y 3, se puede hacer:

void bar(int i) { 
    static void (*lookup[])(void) = { &A<0>::foo, &A<1>::foo, &A<2>::foo, &A<3>::foo }; 
    lookup[i](); 
} 

Por supuesto, he elegido la opción más sencilla para el ejemplo. Si el número que necesita no es consecutivo o está basado en cero, puede preferir un std::map<int, void (*)(void) > en lugar de una matriz. Si la cantidad de opciones diferentes que desea utilizar es mayor, es posible que desee agregar el código para intactar automáticamente las plantillas, en lugar de escribirlas manualmente ... Pero debería tener en cuenta que cada instanciación de la plantilla crea una nueva función, y es posible que desee comprobar si realmente lo necesita.

EDIT: He escrito un post implementando la misma inicialización usando solo las características de C++ 03, parecía demasiado largo para una respuesta.

Luc Danton escribió una respuesta interesante here que incluye, entre otras cosas, la inicialización de la tabla de búsqueda utilizando construcciones C++ 0x. No me gusta mucho de esa solución que cambia la interfaz para requerir un argumento adicional, pero que puede resolverse fácilmente a través de un despachador intermedio.

+0

Eso es lo que estaba buscando. ¿Puede mostrarme cómo inicializar automáticamente la matriz o el mapa (o dirigirme a alguna parte)? – Predrag

+0

@Predrag: ¿Cuántos valores diferentes quieres? ¿Son consecutivos? basado en cero? También tenga en cuenta que cada instancia creará una nueva función, y que a su vez significa que hará que su binario crezca ... –

+0

Son de base cero y sé en tiempo de compilación cuántos de ellos existen. – Predrag

Cuestiones relacionadas