开发者

Unable to pass null function-pointer as template argument

I would like to pass a function as a template argument to another function so that it can be stored and called back later. In some cases, I want to pass NULL for the call back, but I'm having trouble. Here's an example of what I'd like to be able to do:

#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;
}

I found this similar question, but that deals with passing null as an argument to the constructor, not passing null as a template argument, and the trick there (using (Foo*)0) doesn't work as a template argument.

Is there a way to work around this or do some tricky template specialisation or some other clever thing to get the desired effect?

EDIT:

The above was a simplified example that illustrated the problem I was having, but here's the concrete problem I'm trying to solve. I have this project I'm working on. This is a set of functions that make mixing C++ and Lua simpler for me (For various reasons I don't want to use LuaBind or the other existing functions I've found out there). The important function to this question is luaW_register<T>, near the bottom. This is a slightly out of date version, but it works in almost a开发者_如何学Goll cases. It doesn't work, however, if the constructor is private, which has just come when I tried mixing this with Box2D's b2Body (which needs to be made from a b2World). luaW_defaultallocator<T>() (and luaW_defaultdeallocator<T>()) still gets created since I'm using it as the default argument in luaW_register<T>().

My proposed solution was to pull the allocator parameter out into template parameters of luaW_Register. Then, if I want to use some other function to get my objects for a specific type, luaW_defaultallocator will not even be created. In cases like b2Bodys, where they can't create themselves at all, I would like to be able to just pass in NULL as a template argument (which seems perfectly reasonable, but the compiler is choking on it for reasons that are still unclear to me, it seems like if I can set a value to NULL anywhere else in code I ought to be able to for templates as well). A hack I initially implemented was to pass in a boolean argument to my function which would disable the ability to call Foo.new from my Lua code, but that doesn't stop defaultallocator from compiling, and if I can use the null check in and working the way I would like it has the nice side effect of letting me simply check if there's an allocator and use that to control whether or not the new function gets added to the lua table.

tl;dr: my goal was to go from this:

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>)

to this:

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)

to avoid instantiation of luaW_defaultallocator in some cases, but it's looking like it might not be possible.

The closest solution I've seen so far is to provide a function like luaW_cannotalloc<T>(lua_State*) which returns NULL and can be checked for in my luaW_register function instead of null. I suppose that would work, but it means more typing and needing to remember that function name, and NULL seems much cleaner.


This can be solved by using template overloads. Instead of having just one ´T_new` signature you´ll have one signature for the NULL case and one for the other:

// 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;
}

The trick is to realize that NULL is actually an int and use this to handle the NULL case in a different overload.


The problem for null-pointer is that a template pointer argument must have external linkage. And null doesn't have linkage.

How to make things work: It seems that you have selected the wrong tool for whatever it is you're trying to achieve.

Cheers & hth.,


You can (I think) set up a constant of the appropriate type and use it:

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

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

Or in C++0x, you should be able to use the new nullptr keyword.

Or you can do what the comment suggests, and simplify the logic by just making an actual function that returns null, instead of special casing for a null function pointer:

Foo* make_null_foo() { return 0; }

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

i.e. the Null Object pattern.


It's ugly, but it works:

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


I don't really think that templates make sense here, and I am not sure that you have really thought this through... why would you statically call a function knowing that it will return NULL?

Anyway you can do this:

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

Or if you need to go through intermediate templates (i.e. you might not know at a given point whether the function will be null or not, you can follow the null object pattern and provide a null-function:

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 >();

Alternatively, forget about using the function pointer as argument to the template and pass it as a real argument to the function:

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

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜