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)
.
¿Puedo hacer que el compilador lo desenrolle si sé (en tiempo de compilación) cuántos casos hay? – Predrag
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
@Predrag Elija un límite superior lo suficientemente grande y con el que se sienta cómodo. –