【问题标题】:Unable to pass null function-pointer as template argument无法将空函数指针作为模板参数传递
【发布时间】:2011-07-09 09:35:54
【问题描述】:

我想将一个函数作为模板参数传递给另一个函数,以便以后可以存储和调用它。在某些情况下,我想为回调传递 NULL,但我遇到了麻烦。这是我想做的一个例子:

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

我发现this 类似的问题,但它处理将 null 作为参数传递给构造函数,而不是将 null 作为模板参数传递,并且那里的技巧(使用 (Foo*)0)不能用作模板参数.

有没有办法解决这个问题,或者做一些棘手的模板专业化或其他一些聪明的事情来获得预期的效果?

编辑:

上面是一个简化的示例,说明了我遇到的问题,但这是我要解决的具体问题。我有this project 我正在努力。这是一组使混合 C++ 和 Lua 对我来说更简单的函数(出于各种原因,我不想使用 LuaBind 或我在那里找到的其他现有函数)。这个问题的重要功能是luaW_register&lt;T&gt;,靠近底部。这是一个稍微过时的版本,但它几乎适用于所有情况。但是,如果构造函数是私有的,它就不起作用了,当我尝试将它与 Box2D 的 b2Body (需要由 b2World 制作)混合时,它才出现。 luaW_defaultallocator&lt;T&gt;()(和luaW_defaultdeallocator&lt;T&gt;())仍然被创建,因为我将它用作luaW_register&lt;T&gt;() 中的默认参数。

我提出的解决方案是将allocator 参数拉出到luaW_Register 的模板参数中。然后,如果我想使用其他函数来获取特定类型的对象,luaW_defaultallocator 甚至不会被创建。在像b2Bodys 这样他们根本无法创建自己的情况下,我希望能够将NULL 作为模板参数传入(这似乎非常合理,但编译器出于某种原因而窒息这对我来说仍然不清楚,似乎如果我可以在代码中的其他任何地方设置一个值NULL,我也应该能够为模板设置)。我最初实现的一个技巧是向我的函数传递一个布尔参数,这将禁用从我的 Lua 代码调用 Foo.new 的能力,但这不会阻止 defaultallocator 编译,如果我可以使用 null 检查以我希望的方式工作并具有很好的副作用,让我可以简单地检查是否有分配器并使用它来控制 new 函数是否被添加到 lua 表中。

tl;dr:我的目标是从此开始:

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

到这里:

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)

在某些情况下避免 luaW_defaultallocator 的实例化,但它看起来可能是不可能的。

到目前为止,我看到的最接近的解决方案是提供一个类似 luaW_cannotalloc&lt;T&gt;(lua_State*) 的函数,它返回 NULL 并且可以在我的 luaW_register 函数中检查而不是 null。我想这会起作用,但这意味着更多的输入和需要记住函数名,而且 NULL 看起来更清晰。

【问题讨论】:

  • 为什么不能只传递一个返回 null 的函数?然后,您也可以简单地从 T_new 中删除 if
  • 我不知道“棘手的模板专业化”对您意味着什么。对第二个参数进行部分特化是什么问题是 NULL 删除 if 并将 else 子句放在专用版本中?
  • @Conspicuous:如果您可以编写一个工作示例,请分享。我想不通。
  • @Conspicuous:不允许对函数进行部分模板特化。
  • 顺便说一句,这在 VS2008 上编译得很好

标签: c++ templates


【解决方案1】:

这可以通过使用模板重载来解决。您将拥有一个用于 NULL 情况的签名和一个用于其他情况的签名,而不是只有一个“T_new”签名:

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

诀窍是认识到 NULL 实际上是一个 int 并使用它来处理不同重载中的 NULL 情况。

【讨论】:

  • 你不能使用函数的部分模板特化。
  • @David:谢谢,编辑了我的答案,现在说重载而不是专业化。
  • 使用这个 hack 似乎是迄今为止我想要实现的最佳解决方案。由于我实际上有 3 个要传递的函数指针(因此我需要制作 6 个原型),因此我需要做更多的工作,但否则它会达到预期的结果。
  • @Alf:我从未使用过nullptr(或其他C++0x 特性),但我认为它属于nullptr_t 类型。如果我是对的,必须添加一个新的重载template&lt;typename T, nullptr_t func&gt; T* T_new()
【解决方案2】:

空指针的问题是模板指针参数必须有外部链接。而且 null 没有链接。

如何让事情发挥作用:您似乎为想要实现的目标选择了错误的工具。

干杯,

【讨论】:

  • 您的回答没有帮助,因为它没有提供任何有用的信息,无论是如何解决问题或完全回避问题的替代解决方案。你所说的只是“你有问题,用不同的方式解决它”,这对我一点帮助都没有。我也不同意我为这项工作选择了错误的工具。它可能是该示例的错误工具,但为了说明我看到的编译器错误,它已经过简化。有关我正在尝试解决的问题的更多详细信息,请参阅我的编辑,如果您认为自己有更好的工具,请告诉我。
  • @Alex:您当前选择的“解决方案”仅提供您想要的顶级调用站点 notation,假装为函数指针传递 NULL。它实际上并没有传递 NULL 函数指针。对于符号,您最好只删除指针模板参数。在写这个答案的时候,我没有认真考虑过。如果您说您只想要符号,那么这些其他答案(我相信每个受访者都认为有用,我当然确实如此)都不会给出。
【解决方案3】:

您可以(我认为)设置适当类型的常量并使用它:

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

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

或者在 C++0x 中,您应该可以使用新的 nullptr 关键字。

或者您可以按照评论的建议进行操作,并通过创建一个返回 null 的实际函数来简化逻辑,而不是为 null 函数指针使用特殊的大小写:

Foo* make_null_foo() { return 0; }

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

即空对象模式。

【讨论】:

  • 尝试遵循您的第一个示例会产生此错误:'make_null_foo 未在此范围内声明 test.cpp:40:26:错误:make_null_foo 不能出现在常量表达式中`。至于第二种解决方案,我不想这样做的原因是我必须为每个我不想制作 Foo 的地方编写一个函数。它还简化了代码中其他地方的内容。
  • 为什么不能到处重复使用同样的非 Foo-making 函数?
  • 我可以,但我必须将其设为模板函数,因为这将用于许多不同的类型。如果不允许你创建新对象,我想要一些不同的行为,如果我可以传递 NULL,那么我只需要一个简单的空指针检查,看看你是否被允许创建新对象。如果我找不到更好的解决方案,我可能最终会选择那个选项,它只会让我的代码不那么漂亮,这让我很难过:-/
  • 第一个例子的一个问题是(Foo*) 是一个类型转换。它可能只适用于Foo*
  • 被诅咒的函数指针语法... >:/ @Alex: 你有没有考虑过不使用模板?运行时解决方案往往更灵活。实际上,你到底真正想在这里做什么?
【解决方案4】:

它很丑,但它有效:

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

【讨论】:

    【解决方案5】:

    我真的不认为模板在这里有意义,而且我不确定你是否真的考虑过这一点......为什么你会静态地调用一个知道它将返回 NULL 的函数?

    无论如何你都可以这样做:

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

    或者,如果您需要通过中间模板(即,您可能在给定点不知道该函数是否为 null,您可以遵循空对象模式并提供一个 空函数

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

    或者,忘记使用函数指针作为模板的参数并将其作为真实参数传递给函数:

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

    【讨论】:

    • 我在原始问题中添加了更多信息。我的示例是我实际上试图解决的问题的一个非常简化的版本,以避免包含无关的细节,但人们一直在询问我的理性,所以我将其包括在内。
    猜你喜欢
    • 2010-11-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-09-10
    • 2011-08-01
    • 2020-10-26
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多