5

假设我有一个函数执行一些副作用,然后返回一个答案:

int foo()
{
    perform_some_side_effect();
    return 42;
}

我想绑定foo到一个函数指针,但我对答案不感兴趣,只是副作用:

void (*bar)() = foo;

但是,这似乎是一个类型错误:

error: invalid conversion from ‘int (*)()’ to ‘void (*)()’

该错误背后的原因是什么?为什么类型系统不允许我忽略答案?


附带说明一下,如果我将函数指针包装在 a 中,它会起作用std::function

std::function<void()> baz = foo;

(显然)如何std::function设法规避类型系统中的这种限制?

4

4 回答 4

9

该错误背后的原因是什么?为什么类型系统不允许我忽略答案?

原因是类型不同,调用处(通过函数指针)生成的代码不同。考虑一个调用约定,其中所有参数都写入堆栈,返回值的空间也保留在堆栈中。如果调用通过 a void (*)(),则堆栈中不会为返回值保留空间,但函数(不知道它是如何被调用的)仍会将 a 写入42调用者应该保留空间的位置。

std::function (显然)如何设法规避类型系统中的这种限制?

它不是。它创建一个函数对象,将调用包装到实际函数中。它将包含一个成员,例如:

void operator()() const {
   foo();
}

现在,当编译器处理对它的调用时,foo它知道调用返回 an 的函数必须做什么,int并且它将根据调用约定执行此操作。因为模板没有返回,它只会忽略实际返回的值。

于 2012-06-03T01:22:47.540 回答
1

除了其他人所说的,调用者还需要返回类型来知道它应该对结果调用什么析构函数(返回值可能是临时的)。


不幸的是,这并不像

auto (*bar)() = foo; 

尽管 GCC 和 Clang 接受了这一点。我需要重新检查规范,看看这是否真的正确。

更新:规范说

auto类型说明符表示被声明的变量的类型应从其初始化程序中推导出来,或者函数声明符应包含尾随返回类型。

这在快速阅读时可能会产生误导,但这是由 GCC 和 clang 实现的,仅适用于顶级声明符。在我们的例子中,这是一个指针声明符。嵌套在其中的声明符是一个函数声明符。所以只需替换autovoid然后编译器将为您推断类型。


顺便说一句,您总是可以手动完成这项工作,但要使其工作需要一些技巧

template<typename FunctionType>
struct Params; 

template<typename ...Params>
struct Params<void(Params...)> {
  template<typename T>
  using Identity = T;

  template<typename R>
  static Identity<R(Params...)> *get(R f(Params...)) {
    return f;
  }
};

// now it's easy
auto bar = Params<void()>::get(foo);
于 2012-06-03T11:19:17.260 回答
1

std::function只需要与源代码兼容——也就是说,它可以生成一个新类,该类会生成忽略结果的新校准代码。函数指针必须是二进制兼容的,并且不能完成这项工作void(*)()——并且int(*)()指向完全相同的代码。

于 2012-06-02T23:45:05.743 回答
1

您可以考虑std::function<>针对您的特定情况执行此操作:

void __func_void()
{
    foo();
}

它实际上比这要复杂一些,但关键是它会生成模板代码和类型擦除,而不关心细节。

于 2012-06-02T23:46:24.620 回答