【问题标题】:externally defined function pointer as template argument外部定义的函数指针作为模板参数
【发布时间】:2012-01-22 09:24:29
【问题描述】:

考虑以下代码:

main.cpp:

#include <iostream>
typedef void ( * fncptr)(void);
extern void externalfunc(void);

template<void( * test)(void)>
class Bar
{
public:
    Bar() { test(); }
};

void localfunc()
{
    std::cout << "Hello World" << std::endl;
}
constexpr fncptr alias = localfunc;
extern fncptr externalAlias;
int main(int argc, char* argv[])
{
    Bar<localfunc> b;
    Bar<alias> b2; // This wouldn't compile if alias wasn't constexpr
    Bar<externalfunc> b3;
//  Bar<externalAlias> b4;

    return 0;
}

和external.cpp:

#include <iostream>

typedef void ( * fncptr)(void);

void externalfunc()
{
    std::cout << "Hello external world" << std::endl;
}

fncptr externalAlias = externalfunc;

现在的问题是我需要主函数中的第 4 行之类的东西才能工作。我从外部 C 库中获取这些函数声明,所以我无法触及它们。目前第 4 行无法编译。 gcc 4.6 说“它必须是具有外部链接的函数的地址”。事实上,如果你让alias 不是 constexpr,它也会这么说,所以实际含义(我认为)应该解释为:“我不知道 100% 确定你给我的函数地址是恒定的我需要实例化这个模板”。有什么办法可以解决这个问题,因为我不能在main.cpp 中将externalalias 声明为constexpr

在您使用替代解决方案之前:我已经在尝试通过将函数指针传递给构造函数并将它们保存在本地来完成这项工作,但我仍然感兴趣是否可以让模板版本正常工作:)。

【问题讨论】:

  • 编译器应该如何知道externalAlias 的值是什么,以便实例化模板?
  • 是的,它不知道externalAlias 的值,但它是一个变量,因此它在编译时知道该变量的位置,因此它可以在其上进行模板化。 (如另一个答案所示(即我的答案:-)))。
  • 为什么要使用模板进行类专业化?这就是继承的目的。 (即 test() 应该只是 Bar 的纯虚拟方法)然后在派生类中实现 test 对应于 localfunc、alias、externfunc 等......或者更好的是,Bar 只是一个独立的类,并且在它的构造函数中传递了一个实例和“ITest”类。使用模板做一些棘手的事情只会导致其他人继承无法维护的代码。
  • @selbie:我不想做类专业化,那个例子只是最小化的代码来显示问题。实际的问题实际上是我有一种资源逻辑/处理,我需要包装 3-4 个 C 函数,但是对于每种资源都有不同的功能(只是名称不同,而不是语法)。目前我只是为每组这些函数复制粘贴一个类,只替换类名和调用的实际函数,听起来很像手动模板......
  • @KillianDS - 我也这么认为。尽管如此,听起来您正在使用模板做一些极端的事情,而具有虚拟方法和接口的策略模式可能会这样做。在我的产品团队中,非常不鼓励使用模板——而且有充分的理由——它们可能更难调试和维护。通常对(接口)继承的一些应用就足够了。对于没有真正回答您的问题,我深表歉意。但我只是想鼓励您摆脱模板,而转向更易于维护的东西。 en.wikipedia.org/wiki/Strategy_pattern

标签: c++


【解决方案1】:

名称aliasexternAlias不是 函数!它们是指向函数的指针,因此是可变的。您不能将可变对象用作模板参数,因为模板参数需要在编译时解析。但是,您可以做两件事:

  1. 您可以使用您选择给包装器的任何名称创建一个调用潜在别名函数的包装器。如果函数签名之间存在一定程度的差异,这也可以方便地调整类型。
  2. 您可以使用指向函数指针的常量指针作为模板参数:通常的多一层间接假设也适用于此。

说了这么多,你到底想达到什么目的?你确定你不只是想要像std::function&lt;void(void)&gt; 这样的东西吗?我意识到这用于类型擦除,而您似乎有相反的目标并将函数指针转换为唯一类型。但是,您的做法似乎并不是您真正打算做的。

下面是第二个选项的示例:

#include <iostream>

extern void f(int);
extern void g(int);

void (*falias)(int) = f;
void (*galias)(int) = g;

template <void (**alias)(int)>
struct foo
{
    void bar() { (*alias)(17); }
};

void f(int x) { std::cout << "f(x)=" << x << "\n"; }
void g(int x) { std::cout << "g(x)=" << x << "\n"; }

int main()
{
    foo<&falias>().bar();
    foo<&galias>().bar();
}

f()g() 最终在同一个翻译单元中实现这一事实完全无关紧要:您可以毫无问题地将它们移动到其他地方。

【讨论】:

  • 我正在使用一个具有特定 callFlow 的库,例如“createX、doSomethingWithX、deleteX”,但 X 因各种目的而不同。由于围绕这些调用的逻辑对于所有用途都是相同的,因此我尝试将this 在单个模板化容器中并将这 3 个函数指针传递给该模板,从而减少为每个 X 复制此功能的需要。每个 X 的类类型也应该是唯一的(由于某些静态)。
  • 在这种情况下为什么不能使用&amp;createX&amp;doSomethingWithX&amp;deleteX?这些似乎是库中的功能。如果它们不是但可能是别名(即通过函数指针调用),则必须创建某种包装器。 ...或使用选项 2。
  • 我尝试过像您的示例一样,它适用于我原始示例的localfunc,但不适用于externalfunc。但我可能会选择选项 1 并添加另一个包装器 :)。
【解决方案2】:

如果您准备好手动注释类型一点,这是可能的。归根结底,各种对象的类型是有细微差别的。

我会倒着写这个答案,先看main

int main(int argc, char* argv[])
{
    Bar<void(*)(void) , localfunc> b;
    Bar<void(**)(void), &alias> b2;
    Bar<void(*)(void) , externalfunc> b3;
    Bar<void(**)(void), &externalAlias> b4;

    return 0;
}

您可能需要考虑使用typeof(g++) 或decltype(c++11) 来创建更具可读性的版本,并且可能使用宏:

Bar<decltype(&localfunc), localfunc> b;
Bar<decltype(&alias), &alias> b2;
Bar<decltype(&externalfunc), externalfunc> b3;
Bar<decltype(&externalAlias), &externalAlias> b4;

我不得不稍微更改一下 Bar 模板:

template<typename T, T t>
class Bar;

以便它可以处理函数指针和指针到函数指针。这需要两个专业:

template<void( * test)(void)>
class Bar<void(*)(void), test>
{
public:
    Bar() { std::cout << "* "; test(); }
};
template<void( ** test)(void)>
class Bar<void(**)(void), test>
{
public:
    Bar() { std::cout << "**"; (*test)(); }
};

应该可以为具有不同参数类型和返回类型的其他类型的函数添加进一步的特化,甚至包括方法调用。

我在自己的机器上对此进行了全面测试。这是ideone上的演示(没有外部函数)。

注意:对于别名,模板参数是别名的地址,因此它将考虑您在运行时对别名变量所做的任何更改。我想您可能希望将别名设为 const。 const fncptr alias = localfunc;

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-04-13
    • 2021-12-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多