2012-01-20 26 views
11

decir que tengo una macro como esto:¿Cómo pasar plantillas de múltiples argumentos a macros?

#define SET_TYPE_NAME(TYPE, NAME) \ 
    template<typename T>   \ 
    std::string name();   \ 
            \ 
    template<>     \ 
    std::string name<TYPE>() { \ 
     return NAME;    \ 
    } 

Esto no funcionará si lo paso una plantilla que tiene más de un parámetro, ya que el coma en el <int, int> se interpreta como la separación de las macro argumentos , no los argumentos de la plantilla.

SET_TYPE_NAME(std::map<int, int>, "TheMap") 
// Error: macro expects two arguments, three given 

Este problema parece estar resuelto al hacer esto:

SET_TYPE_NAME((std::map<int, int>), "TheMap") 

Pero ahora surge otro problema, uno que realmente no esperaba:

template<> 
std::string name<(std::map<int, int>)>() 
// template argument 1 is invalid 

Parece que el extra los paréntesis hacen inválido el argumento de la plantilla. ¿Hay alguna forma de evitar esto?

Respuesta

8

Además typedef, usted podría cambiar el orden de los argumentos y utilizar macros variadic (requiere C99 o compilador de C++ compatibles-11):

#define SET_TYPE_NAME(NAME, ...) \ 
template<typename T>   \ 
std::string name();   \ 
           \ 
template<>     \ 
std::string name<__VA_ARGS__>() { \ 
    return NAME;    \ 
} 

...

SET_TYPE_NAME("TheMap", std::map<int, int>) 
+6

Sólo C++ 11, a menos C99 añadió 'template' mientras estábamos sin mirar. –

+0

@KennyTM: ¡Maldito, un minuto por delante de mí! –

+0

@MikeDeSimone: __VA_ARGS__ se introdujo en C99, por lo que un compilador de C++ (por ejemplo, gcc 4.2) podría admitirlo en C++ como una extensión no estándar. – kennytm

8

Usted podría utilizar typedef:

typedef std::map<int, int> int_map; 

SET_TYPE_NAME(int_map, "TheMap"); 

BOOST_FOREACH sufre de impulso de la misma cuestión.

1
typedef std::map<int,int> IntMap_t; 
SET_TYPE_NAME(IntMap_t, "TheMap") 

Se puede declarar un typedef y utilizarlo en la macro

3

me gusta la forma typedef prop osed por hmjd mejor, pero para el registro, la forma habitual que he visto de evitar esto es para poner los paréntesis angulares de la macro y escribir:

#define SET_TYPE_NAME(TYPE, NAME) \ 
    template<typename T>   \ 
    std::string name();   \ 
            \ 
    template<>     \ 
    std::string name TYPE() { \ 
     return NAME;    \ 
    } 

Uso:

SET_TYPE_NAME(<std::map<int, int> >, "TheMap") 

Este es una variación de una vieja técnica utilizada para la presentación de informes mensaje de error y fprintf:

#define Error(args) do { \ 
    printf("ERROR: "); \ 
    printf args; \ 
    printf("\n"); \ 
    return 1; \ 
    } while(0) 

Llamado con:

Error(("Index out of range: %d not in %d ... %d.", var, min, max)); 

Es feo pero funcionó. Útil si las reglas de estilo de codificación prohíben typedef.

+1

Interesante, ¿pero esto no provoca el primer error nuevamente? En el que la macro interpreta la plantilla como dos argumentos. –

+2

Útil si las reglas de estilo de codificación prohíben 'typedef' -> ahora me tienes preocupado. –

+2

Desafortunadamente, solo paréntesis, y no '<' and '>', son reconocidos como corchetes por el preprocesador, por lo que esto no funcionará. –

5

Hace algún tiempo (mientras buscaba en la web la utilidad de Identity<T>) llegué al this page.

En resumen, para responder a la pregunta, si no tiene compatibilidad con C++ 11 y/o no puede (o no desea) utilizar un typedef, llame a su macro la forma "correcta" :

// If an argument contains commas, enclose it in parentheses: 
SET_TYPE_NAME((std::map<int, int>), "TheMap") 
// For an argument that doesn't contain commas, both should work: 
SET_TYPE_NAME((SomeType1), "TheType1") 
SET_TYPE_NAME(SomeType2, "TheType2") 

y luego para deshacerse de las (posibles) paréntesis no deseados en torno al tipo, puede utilizar un "ayudante" de esta manera:

template<typename> struct RemoveBrackets; 
template<typename T> struct RemoveBrackets<void (T)> { 
    typedef T Type; 
}; 

y en su cambio macro la línea :

std::string name<TYPE>() { \ 

a:

std::string name< RemoveBrackets<void (TYPE)>::Type >() { \ 

(o definir una macro ayudante, dicen

#define REMOVE_BRACKETS(x) RemoveBrackets<void (x)>::Type 

vuelva a colocar la línea con

std::string name< REMOVE_BRACKETS(TYPE) >() { \ 

).

(Para la historia completa leer el párrafo "Una mejor solución" al final del artículo enlazado más arriba.)

Editar: acaba de encontrar that. Pero usa <void X> cuando realmente debería usar <void (X)> (en get_first_param<void X>::type); de hecho los paréntesis son necesarios si pasa un argumento "simple", sin corchetes (como SomeType2 en mi código anterior) - y no hacen daño si X ya está entre corchetes (por ejemplo, void ((SomeType1)) es equivalente a void (SomeType1); otra vez , mira el artículo). (Por cierto, noté que muchas respuestas en la otra página SO son, en esencia, "Las macros son tontas". No voy a comentar, sin embargo).

+0

Editar: [Ahora puede simplemente usar 'BOOST_IDENTITY_TYPE' (gracias a OP)] (http://stackoverflow.com/a/11701846/688659) –

4

Me gustaría añadir una respuesta a mi propia pregunta. Ahora que se lanzó Boost 1.50.0, Boost.Utility tiene una nueva mini biblioteca que ayuda con este tipo de cosas. Si miras la fuente, verás que está implementada igual que gx_'s solution. Así es como se usa:

#include <boost/utility/identity_type.hpp> 
SET_TYPE_NAME(BOOST_IDENTITY_TYPE((std::map<int, int>)), "TheMap"); 
+0

_ (Puedo dejar comentarios ahora !) _ Gracias por la noticia :) –

+0

Esta es la mejor respuesta, ¡gracias! – spatz

1

Una forma más genérica es utilizar siempre() alrededor de los argumentos que pueden contener una coma y utilizar CONCAT para eliminar los paréntesis. Si lo hace, puede definir varios parámetros packs ingenio una coma dentro o poner argumentos en el orden que prefiera

#ifndef CONCAT 
#define CONCAT __VA_ARGS__ 
#endif 

#define MYMACRO(tparam,classname)\ 
    template < CONCAT tparam >\ 
    class CONCAT classname {}; 

//create a template class X<T,U> 
MYMACRO((typename T,typename U) , (X<T,U>)) 
Cuestiones relacionadas