有没有办法存储通用的模板化函数指针?

时间:2020-08-21 18:04:13

标签: c++ templates

目标:

在运行时确定要使用的模板函数,然后在不需要类型信息的情况下使用它。

部分解决方案:

对于参数本身未模板化的函数,我们可以做到:

int (*func_ptr)(void*) = &my_templated_func<type_a,type_b>;

可以修改此代码行,以在type_atype_b的if语句中使用不同类型,从而为我们提供了一个模板化函数,其类型在运行时确定:

int (*func_ptr)(void*) = NULL;
if (/* case 1*/)
  func_ptr = &my_templated_func<int, float>;
else
  func_ptr = &my_templated_func<float, float>;

仍然存在的问题

当参数是模板指针时,该怎么办?

例如,这是我想做的事情:

int (*func_ptr)(templated_struct<type_a,type_b>*); // This won't work cause I don't know type_a or type_b yet
if (/* case 1 */) {
  func_ptr = &my_templated_func<int,float>;
  arg = calloc(sizeof(templated_struct<int,float>, 1);
}
else {
  func_ptr = &my_templated_func<float,float>;
  arg = calloc(sizeof(templated_struct<float,float>, 1);
}

func_ptr(arg);

除了我希望type_a和type_b在运行时确定。我看到了部分问题。

  1. 函数指针的类型是什么?
  2. 如何调用此函数?

我认为我对(2)有答案:只需将参数强制转换为void*,并且模板函数应使用函数定义进行隐式强制转换(如果这不起作用,请更正我,会的。)

(1)是我遇到问题的地方,因为函数指针必须包含参数类型。这与部分解决方案不同,因为对于函数指针定义,我们能够“忽略”函数的模板方面,因为我们真正需要的只是函数的地址。

或者,有一种更好的方法可以实现我的目标,如果是的话,我会全力以赴。

由于@Jeffrey的回答,我得以提出一个简短的示例来说明我要完成的工作:

template <typename A, typename B>
struct args_st {
  A argA;
  B argB;
}

template<typename A, typename B>
void f(struct args_st<A,B> *args) {}

template<typename A, typename B>
void g(struct args_st<A,B> *args) {}

int someFunction() {
  void *args;

  // someType needs to know that an args_st struct is going to be passed
  // in but doesn't need to know the type of A or B those are compiled
  // into the function and with this code, A and B are guaranteed to match
  // between the function and argument.
  someType func_ptr;
  if (/* some runtime condition */) {
     args = calloc(sizeof(struct args_st<int,float>), 1);
     f((struct args_st<int,float> *) args); // this works
     func_ptr = &g<int,float>; // func_ptr should know that it takes an argument of struct args_st<int,float>
  }
  else {
     args = calloc(sizeof(struct args_st<float,float>), 1);
     f((struct args_st<float,float> *) args); // this also works
     func_ptr = &g<float,float>; // func_ptr should know that it takes an argument of struct args_st<float,float>
  }

  /* other code that does stuff with args */

  // note that I could do another if statement here to decide which
  // version of g to use (like I did for f) I am just trying to figure out
  // a way to avoid that because the if statement could have a lot of
  // different cases similarly I would like to be able to just write one
  // line of code that calls f because that could eliminate many lines of
  // (sort of) duplicate code
  func_ptr(args);

  return 0; // Arbitrary value
}

5 个答案:

答案 0 :(得分:1)

如果我理解正确,那么您想做的事情归结为:

template<typename T> 
void f(T)
{
}

int somewhere()
{
    someType func_ptr;
    int arg = 0;

    if (/* something known at runtime */) 
    {
        func_ptr = &f<float>;
    }
    else
    {
        func_ptr = &f<int>;
    }

    func_ptr(arg);
}

您不能在C ++中做到这一点。 C ++是静态类型的,模板类型都在编译时解析。如果构造允许您执行此操作,则编译器将无法知道必须使用哪些类型实例化哪些模板。

替代方法是:

  • 运行时多态性的继承
  • 如果您要处理基础类型,请在任何地方
  • C样式void *

编辑:

阅读已编辑的问题:

func_ptr应该知道它接受struct args_st<float,float>

的参数

func_ptr应该知道它接受struct args_st<int,float>

的参数

不兼容。在C ++中,完成此操作的方式是根据所需要的类型键入func_ptr。不能同时/全部/任意。

如果存在func_ptr的类型,以便它可以接受任意类型的参数,则可以在函数和编译单元之间传递它,而您的语言将突然不会被静态键入。您最终将使用Python ;-p

答案 1 :(得分:1)

不能使用std :: function并使用lambda捕获所需的所有内容吗?您的函数似乎没有带参数,因此可以正常工作。 即

std::function<void()> callIt;

if(/*case 1*/)
{
     callIt = [](){ myTemplatedFunction<int, int>(); }
} 
else 
{
     callIt = []() {myTemplatedFunction<float, float>(); }
}

callIt();

答案 2 :(得分:0)

也许您想要这样的东西:

FutureBuilder(
              future: http.get("http://127.0.0.1:8010"),
              builder: (context, data) {
                if (data.hasData)
                  return Text(
                    data.toString(),
                    style: TextStyle(fontSize: 20),
                  );
                else if (data.hasError) {
                  print(data.error);
                  return Text(
                    data.error.toString(),
                    style: TextStyle(fontSize: 20),
                  );
                } else
                  return Text(
                    "No data",
                    style: TextStyle(fontSize: 20),
                  );
              }),

采用不同参数的函数具有不同的类型,因此不能将#include <iostream> template <typename T> void foo(const T& t) { std::cout << "foo"; } template <typename T> void bar(const T& t) { std::cout << "bar"; } template <typename T> using f_ptr = void (*)(const T&); int main() { f_ptr<int> a = &bar<int>; f_ptr<double> b = &foo<double>; a(1); b(4.2); } 指向f_ptr<int>。否则,您可以像实例化其他函数一样将实例化函数模板所获得的函数存储在函数指针中,例如,您可以让bar<double>持有f_ptr<int>&foo<int>

答案 3 :(得分:0)

您选择的手动内存管理和关键字struct的过度使用表明您来自C背景,尚未真正转换为C ++编程。结果,有许多需要改进的地方,您可能会发现应该抛弃当前的方法。但是,这是未来的一步。其中涉及学习过程,对当前代码进行逐步改进是达到目标的一种方法。

首先,我想摆脱C风格的内存管理。大多数时候,在C ++代码中使用calloc是错误的。让我们用智能指针替换原始指针。 shared_ptr看起来会帮助完成该过程。

// Instead of a raw pointer to void, use a smart pointer to void.
std::shared_ptr<void> args;

// Use C++ memory management, not calloc.
args = std::make_shared<args_st<int,float>>();
// or
args = std::make_shared<args_st<float,float>>();

这仍然不是很好,因为它仍然使用指向void的指针,除非与C编写的库进行接口连接,否则在C ++代码中很少使用该指针。但这是一种改进。使用指向void的指针的一个副作用是需要强制转换以返回到原始类型。应该避免这种情况。我可以在您的代码中通过在if语句中定义正确类型的变量来解决此问题。一旦正确键入的变量超出范围,args变量仍将用于保留指针。 沿着这方面的更多改进可能会在以后出现。

我要进行的关键改进是使用功能std::function而不是功能指针。 std::function是函数指针的概括,尽管有更多开销,但能够执行更多操作。为了保证健壮的代码,在这里要保证开销。

std::function的优点在于,调用g()的代码无需知道std::function的参数。这样做的旧样式是std::bind,但是lambda提供了一种更具可读性的方法。在调用函数时,您不仅不必担心args的类型,甚至不必担心args

int someFunction() {
    // Use a smart pointer so you do not have to worry about releasing the memory.
    std::shared_ptr<void> args;
    
    // Use a functional as a more convenient alternative to a function pointer.
    // Note the lack of parameters (nothing inside the parentheses).
    std::function<void()> func;

    if ( /* some runtime condition */ ) {
        // Start with a pointer to something other than void.
        auto real_args = std::make_shared<args_st<int,float>>();

        // An immediate function call:
        f(real_args.get());
        // Choosing a function to be called later:
        // Note that this captures a pointer to the data, not a copy of the data.
        // Hence changes to the data will be reflected when this is invoked.
        func = [real_args]() { g(real_args.get()); };

        // It's only here, as real_args is about to go out of scope, where
        // we lose the type information.
        args = real_args;
    }
    else {
        // Similar to the above, so I'll reduce the commentary.
        auto real_args = std::make_shared<args_st<float,float>>();
        func = [real_args]() { g(real_args.get()); };
        args = real_args;
    }

    /* other code that does stuff with args */
    /* This code is probably poor C++ style, but that can be addressed later. */

    // Invoke the function.
    func();

    return 0;
}

您的下一步可能应该是对这些功能进行一些阅读,以便您了解此代码的作用。那么您应该可以更好地利用C ++的功能。

答案 4 :(得分:0)

免责声明: 我已经提供了an answer that directly addresses the question。在这个答案中,我想回避这个问题,并进行讨论。


根据经验,以下代码结构是大多数过程语言(不仅是C ++)的劣等设计。

    if ( conditionA ) {
        // Do task 1A
    }
    else {
        // Do task 1B
    }

    // Do common tasks

    if ( conditionA ) {
        // Do task 2A
    }
    else {
        // Do task 2B
    }

您似乎已经意识到此设计的缺点,因为您正试图消除在if-else中添加第二个someFunction()的需要。但是,您的解决方案并不尽如人意。

(对于代码可读性和可维护性)通常更好的做法是将常见任务移至单独的功能,而不是尝试在一个功能中完成所有操作。这样,代码结构更像以下代码,其中常见任务已移至函数foo()

    if ( conditionA ) {
        // Do task 1A
        foo( /* arguments might be needed */ );
        // Do task 2A
    }
    else {
        // Do task 1B
        foo( /* arguments might be needed */ );
        // Do task 2B
    }

为演示此经验法则的实用性,让我们将其应用于someFunction()。 ...并消除了动态内存分配的需要...以及一些清理...不幸的是,解决讨厌的void*超出了范围...我将它留给读者评估最终结果。我要指出的一个功能是,不再有理由考虑存储“通用模板化函数指针”,从而使所提问题变得毫无意义。

// Ideally, the parameter's type would not be `void*`.
// I leave that for a future refinement.
void foo(void * args) {
    /* other code that does stuff with args */
}

int someFunction(bool condition) {
    if (/* some runtime condition */) {
        args_st<int,float> args;
        foo(&args);
        f(&args); // Next step: pass by reference instead of passing a pointer
    }
    else {
        args_st<float,float> args;
        foo(&args);
        f(&args); // Next step: pass by reference instead of passing a pointer
    }
    return 0;
}