Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unable to pass null function-pointer as template argument

Tags:

c++

templates

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 all 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.

like image 854
Alex Avatar asked Jul 09 '11 09:07

Alex


2 Answers

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.

like image 165
larsmoa Avatar answered Oct 27 '22 01:10

larsmoa


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.,

like image 34
Cheers and hth. - Alf Avatar answered Oct 27 '22 00:10

Cheers and hth. - Alf