问题是两者function<int()>
和function<int(int)>
都可以从同一个函数构造。这是std::function
VS2010 中的构造函数声明:
template<class _Fx>
function(_Fx _Func, typename _Not_integral<!_Is_integral<_Fx>::value, int>::_Type = 0);
忽略 SFINAE 部分,它几乎可以用任何东西构建。
std::/boost::function
采用一种称为类型擦除的技术,以允许传入任意对象/函数,只要它们在被调用时满足签名。这样做的一个缺点是,当提供一个不能像签名希望的那样调用的对象时,而不是在构造函数中,你会在实现的最深部分(调用保存的函数的地方)出错。
这个问题可以用这个小类来说明:
template<class Signature>
class myfunc{
public:
template<class Func>
myfunc(Func a_func){
// ...
}
};
现在,当编译器为重载集搜索有效函数时,如果不存在完美拟合函数,它会尝试转换参数。转换可以通过函数参数的构造函数发生,也可以通过给函数的参数的转换运算符发生。在我们的例子中,它是前者。
编译器尝试第一次重载a
. 为了使其可行,它需要进行转换。要将 a 转换int(*)()
为 a myfunc<int()>
,它会尝试 的构造函数myfunc
。作为一个可以接受任何东西的模板,转换自然会成功。
现在它尝试与第二个重载相同。构造函数仍然是相同的并且仍然接受任何给它的东西,转换也有效。
在重载集中留下了 2 个函数,编译器是一只悲伤的熊猫,不知道该做什么,所以它只是说这个调用是模棱两可的。
所以最后,Signature
模板的部分在进行声明/定义时确实属于类型,但在您要构造对象时不属于。
编辑:
我全神贯注地回答标题问题,我完全忘记了你的第二个问题。:(
我可以绕过它还是必须保留(烦人的)显式演员表?
Afaik,您有 3 个选项。
TMP(模板元编程)版本非常冗长并且带有样板代码,但它对客户端隐藏了强制转换。可以在这里找到一个示例版本,它依赖于get_signature
部分专用于函数指针类型的元函数(并提供了一个很好的例子,模式匹配如何在 C++ 中工作):
template<class F>
struct get_signature;
template<class R>
struct get_signature<R(*)()>{
typedef R type();
};
template<class R, class A1>
struct get_signature<R(*)(A1)>{
typedef R type(A1);
};
当然,这需要针对您想要支持的参数数量进行扩展,但这是一次完成,然后埋在"get_signature.h"
标题中。:)
我考虑过但立即放弃的另一个选择是 SFINAE,它会引入比 TMP 版本更多的样板代码。
所以,是的,这就是我所知道的选项。希望其中一个对你有用。:)