2011-07-09 13 views
9

Me gustaría pasar una función como argumento de plantilla a otra función para que se pueda almacenar y volver a llamar más tarde. En algunos casos, quiero pasar NULL para la devolución de llamada, pero estoy teniendo problemas. Aquí está un ejemplo de lo que me gustaría ser capaz de hacer:No se puede pasar el puntero a la función nula como argumento de plantilla

#include <iostream> 
struct Foo { 
    int i; 
}; 
template <typename T> 
T* T_new() { 
    return new T(); 
} 
Foo* Foo_new() { 
    return new Foo(); 
} 
template <typename T, T* (*func)()> 
T* T_new() { 
    if (func) 
     return func(); 
    else 
     return NULL; 
} 

int main(void) { 
    // Works 
    Foo* f1 = T_new<Foo>(); 
    std::cout << f1 << std::endl; 

    // Works 
    Foo* f2 = T_new<Foo, Foo_new>(); 
    std::cout << f2 << std::endl; 

    // fails to compile, "no matching function for call to ‘T_new()’" 
    // Foo* f3 = T_new<Foo, NULL>(); 
    // std::cout << f3 << std::endl; 

    return 0; 
} 

He encontrado this pregunta similar, pero que trata de pasar null como un argumento para el constructor, no pasar null como un argumento de plantilla, y el truco allí (usando (Foo*)0) no funciona como un argumento de plantilla.

¿Hay alguna manera de evitar esto o hacer alguna especialización de plantilla complicada o alguna otra cosa inteligente para obtener el efecto deseado?

EDIT:

Lo anterior fue un ejemplo simplificado que ilustra el problema que estaba teniendo, pero aquí está el problema concreto que estoy tratando de resolver. Tengo this project en que estoy trabajando. Este es un conjunto de funciones que hacen que mezclar C++ y Lua sea más simple para mí (por diversas razones no quiero usar LuaBind o las otras funciones existentes que he descubierto). La función importante de esta pregunta es luaW_register<T>, cerca de la parte inferior. Esta es una versión ligeramente desactualizada, pero funciona en casi todos los casos. No funciona, sin embargo, si el constructor es privado, que ha llegado justo cuando he intentado mezclar esto con Box2D de b2Body (que tiene que ser hecho de una b2World). luaW_defaultallocator<T>() (y luaW_defaultdeallocator<T>()) todavía se pone creado ya lo estoy usando como argumento predeterminado en luaW_register<T>().

Mi solución propuesta era sacar el parámetro allocator en los parámetros de plantilla de luaW_Register. Entonces, si quiero usar alguna otra función para obtener mis objetos para un tipo específico, ni siquiera se creará luaW_defaultallocator. En casos como b2Body s, donde no pueden crearse a sí mismos en absoluto, me gustaría ser capaz de pasar justo a NULL como un argumento de plantilla (que parece perfectamente razonable, pero el compilador está ahogando en ella por razones que aún no están claras para mí, parece que si puedo establecer un valor en NULL en cualquier otro lugar en el código I debería ser capaz de crear plantillas también). Un truco que implementé inicialmente fue pasar un argumento booleano a mi función que inhabilitaría la capacidad de llamar al Foo.new desde mi código Lua, pero eso no detiene la compilación de defaultallocator, y si puedo usar el cheque nulo y el funcionamiento del De la manera que me gustaría, tiene el bonito efecto secundario de permitirme simplemente verificar si hay un asignador y usar eso para controlar si la función new se agrega o no a la tabla lua.

tl; dr: Mi objetivo era pasar de esto:

template <typename T> 
void luaW_register(lua_State* L, const char* classname, const luaL_reg* table, const luaL_reg* metatable, const char** extends = NULL, bool disablenew = false, T* (*allocator)() = luaW_defaultallocator<T>, void (*deallocator)(T*) = luaW_defaultdeallocator<T>) 

a esto:

template <typename T, T* (*allocator)() = luaW_defaultallocator<T>, void (*deallocator)(T*) = luaW_defaultdeallocator<T> > 
void luaW_register(lua_State* L, const char* classname, const luaL_reg* table, const luaL_reg* metatable, const char** extends = NULL) 

para evitar la creación de instancias de luaW_defaultallocator en algunos casos, pero es que parece que podría no ser posible.

La solución más cercana que he visto hasta ahora es proporcionar una función como luaW_cannotalloc<T>(lua_State*) que devuelve NULL y puede verificarse en mi función luaW_register en lugar de nulo. Supongo que eso funcionaría, pero significa más tipeo y la necesidad de recordar el nombre de la función, y NULL parece mucho más limpio.

+2

¿Por qué no puede simplemente pasar una función que devuelve nulo? Luego puedes simplemente eliminar el 'if' de T_new también. –

+0

No sé qué significa "especialización de plantilla engañosa" para usted. ¿Qué hay de malo en hacer una especialización parcial para el segundo argumento siendo NULL suprimir el 'si' y poner la cláusula' else' en la versión especializada? –

+0

@Conspicuous: si puede codificar un ejemplo de trabajo, por favor, comparta. No pude resolverlo. – Alex

Respuesta

3

Esto se puede resolver mediante el uso de sobrecargas de plantilla. En lugar de tener una sola firma'T_new` se le realizarán una firma para el caso NULL y uno para el otro:

// Unused signature, no implementation so using this will result in link error 
template<typename T, typename F> 
T* T_new(); 
// NULL overload (NULL is an int) 
template<typename T, int func> 
T* T_new() 
{ 
    assert(func == 0 && "Signature should only be used with NULL"); 
    return NULL; 
} 
// Valid function pointer overload 
template<typename T, T* (*func)()> 
T* T_new() 
{ 
    // I don´t think it´s possible with NULL functions now, but if it is 
    // we'll handle that too 
    if (func) 
     return func(); 
    return NULL; 
} 

El truco es darse cuenta que NULL es en realidad un int y usar esto para manejar el NULL caso en una sobrecarga diferente.

+2

No puede usar la especialización de funciones de plantilla parcial. –

+0

@David: gracias, edité mi respuesta, ahora dice sobrecarga en lugar de especialización. – larsmoa

+0

Usar este truco parece ser la mejor solución hasta ahora para lo que estoy tratando de lograr. Es un poco más trabajo en mi parte final ya que tengo 3 punteros de función que estoy pasando (así que tengo que hacer 6 prototipos) pero de lo contrario logra el resultado deseado. – Alex

3

El problema para el puntero nulo es que un argumento de puntero de plantilla debe tener un enlace externo. Y null no tiene enlace.

Cómo hacer que las cosas funcionen: parece que ha seleccionado la herramienta incorrecta para lo que sea que esté tratando de lograr.

Saludos & HTH,

+3

Su respuesta no fue útil, ya que no proporcionó información útil, ya sea sobre cómo solucionar el problema o una solución alternativa que elude el problema por completo.Todo lo que dijiste fue "Tienes un problema, resuélvelo de una manera diferente", lo cual no es de ninguna ayuda para mí. Tampoco estoy de acuerdo con que haya elegido la herramienta incorrecta para el trabajo. Puede ser la herramienta incorrecta para el ejemplo, pero eso se simplifica para ilustrar el error del compilador que estaba viendo. Vea mi edición para obtener más detalles sobre exactamente el problema que estoy tratando de resolver, y si cree que tiene una herramienta mejor, hágamelo saber. – Alex

+0

@Alex: su "solución" elegida actualmente solo proporciona la notación * del sitio de llamada de nivel superior * que desea, pretendiendo pasar NULL para un puntero de función. En realidad, no pasa un puntero a la función NULL. Para la notación, es mejor que simplemente elimine el argumento de la plantilla del puntero. No lo consideré seriamente al momento de escribir esta respuesta. Si hubiera declarado que solo quería la notación, entonces ninguna de estas otras respuestas (que estoy seguro de que cada encuestado consideró útil, ciertamente lo hice) habría sido dada. –

1

Usted puede (creo) establecer una constante del tipo adecuado y utilizarlo:.

(Foo*)(*make_null_foo)() = 0; 

Foo* f3 = T_new<Foo, make_null_foo>(); 

O en C++ 0x, usted debería ser capaz de use la nueva palabra clave nullptr.

O bien, puede hacer lo que sugiere el comentario, y simplificar la lógica con sólo hacer una función real que devuelve un valor nulo, en lugar de carcasa especial para un puntero de función nula:

Foo* make_null_foo() { return 0; } 

Foo* f3 = T_new<Foo, make_null_foo>(); 

es decir, el patrón de objeto nulo.

+0

El intento de seguir su primer ejemplo crea este error: 'make_null_foo no se declaró en este ámbito test.cpp: 40: 26: error: make_null_foo no puede aparecer en una expresión constante'. En cuanto a la segunda solución, la razón por la que preferiría no hacer eso es que tendría que escribir una función para cada lugar que no quiero que sea Foo. También simplificó las cosas en otras partes del código. – Alex

+0

¿Por qué no puedes reutilizar la misma función que no es Foo en todos lados? –

+0

Podría, pero tendría que hacer una función de plantilla, ya que esto se usará en muchos tipos diferentes. Y quiero algunos comportamientos diferentes si no se te permite hacer objetos nuevos, y si puedo pasar NULL, entonces todo lo que necesito es una verificación trivial del puntero nulo para ver si puedes hacer nuevos objetos. Podría terminar yendo con esa opción si no puedo encontrar una solución mejor, solo hace que mi código sea un poco menos bonito y eso me pone triste: -/ – Alex

0

Es feo, pero funciona:

Foo* f3 = T_new<Foo, (Foo* (*)())NULL>(); 
+3

No funcionó para mí (g ++ 4.3). – vines

0

Realmente no creo que las plantillas tienen sentido aquí, y no estoy seguro de que realmente han pensado en esto ... ¿por qué te estáticamente llamar a una función sabiendo que devolverá NULL?

De todas formas se puede hacer esto:

template <typename T, T* (*func)()> 
T* T_new() { 
    return func(); 
} 
template <typename T> 
T* T_new() { 
    return NULL; 
} 

O si tiene que ir a través de plantillas intermedias (es decir, usted puede no saber en un momento dado si la función será nula o no, puede seguir el patrón de objeto nulo y proporcionar un nulo-función :

template <typename T> 
T* null_new() { return 0; } 

template <typename T, T* (*f)() > 
T* T_new() { 
    return f(); 
} 
// user code: 
X* p = T_new<X, null_new >(); 

Alternativamente, olvidarse de usar el puntero de función como argumento a la plantilla y pasarlo como un argumento real para la función:

template <typename T> 
T* T_new(T* (*func)() = 0) { 
    if (func) 
     return func(); 
    else 
     return NULL; 
} 
+0

He agregado más información a la pregunta original. Mi ejemplo fue una versión muy simplificada del problema que en realidad estaba tratando de resolver para evitar incluir detalles extraños, pero la gente sigue preguntando por mi racionalidad, así que lo he incluido. – Alex

Cuestiones relacionadas