Gho*_*ost 39 c++ c++11 std-function
鉴于以下代码,模糊性背后的原因是什么?我可以绕过它还是我必须保持(讨厌的)显式演员?
#include <functional>
using namespace std;
int a(const function<int ()>& f)
{
return f();
}
int a(const function<int (int)>& f)
{
return f(0);
}
int x() { return 22; }
int y(int) { return 44; }
int main()
{
a(x); // Call is ambiguous.
a(y); // Call is ambiguous.
a((function<int ()>)x); // Works.
a((function<int (int)>)y); // Works.
return 0;
}
Run Code Online (Sandbox Code Playgroud)
有趣的是,如果我a()
用function<int ()>
参数注释掉函数并a(x)
在我的main中调用,则编译正确失败,因为它们之间的类型不匹配x
和function<int (int)>
唯一a()
可用函数的参数.如果编译器在这种情况下失败,为什么在存在这两个a()
函数时会有任何歧义?
我试过VS2010和g ++ v.4.5.两者都给我完全相同的歧义.
Xeo*_*Xeo 40
问题是,无论function<int()>
和function<int(int)>
来自同一个函数构造的.这是std::function
VS2010中构造函数声明的样子:
template<class _Fx>
function(_Fx _Func, typename _Not_integral<!_Is_integral<_Fx>::value, int>::_Type = 0);
Run Code Online (Sandbox Code Playgroud)
忽略SFINAE部分,几乎可以构建任何东西.
std::/boost::function
采用一种称为类型擦除的技术,允许传入任意对象/函数,只要它们在被调用时满足签名即可.这样做的一个缺点是,当提供一个无法像签名那样被调用的对象而不是在构造函数中时,在实现的最深部分(调用保存的函数)中会出现错误.
这个小课可以说明问题:
template<class Signature>
class myfunc{
public:
template<class Func>
myfunc(Func a_func){
// ...
}
};
Run Code Online (Sandbox Code Playgroud)
现在,当编译器搜索重载集的有效函数时,如果不存在完美拟合函数,它会尝试转换参数.转换可以通过函数参数的构造函数发生,也可以通过赋予函数的参数的转换运算符发生.在我们的例子中,它是前者.
编译器尝试第一次重载a
.为了使其可行,它需要进行转换.要将a转换int(*)()
为a myfunc<int()>
,它会尝试构造函数myfunc
.作为一个需要任何东西的模板,转换自然会成功.
现在它尝试与第二次重载相同.构造函数仍然是相同的,仍然采取任何给予它,转换也有效.
在重载集中留下了2个函数,编译器是一个悲伤的熊猫,不知道该怎么做,所以它只是说这个调用是不明确的.
因此,最后,Signature
模板的一部分在进行声明/定义时确实属于该类型,但在您想要构造对象时则不然.
编辑:
我全神贯注地回答标题问题,我完全忘记了你的第二个问题.:(
我可以绕过它还是我必须保持(讨厌的)显式演员?
Afaik,你有3个选择.
创建一个function
适当类型的对象并传递它
function<int()> fx = x;
function<int(int)> fy = y;
a(fx);
a(fy);
在函数中隐藏繁琐的转换并使用TMP来获得正确的签名
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);
};
Run Code Online (Sandbox Code Playgroud)
当然,这需要针对您想要支持的参数的数量进行扩展,但这只需要执行一次,然后将其隐藏在"get_signature.h"
标头中.:)
我考虑但另外选择的另一个选项是SFINAE,它将引入比TMP版本更多的样板代码.
所以,是的,这是我所知道的选择.希望其中一个适合你.:)
How*_*ant 11
我已经看过这个问题太多次了. libc ++现在编译此代码时没有歧义(作为符合标准的扩展).
逾期更新
这种"扩展"证明非常受欢迎,它在C++ 14中被标准化(尽管我个人不负责完成这项工作).
事后来看,我没有得到这个扩展完全正确.本月早些时候(2015-05-09)委员会在LWG问题2420中投票,它有效地改变了Callable的定义,因此如果std::function
有一个void
返回类型,它将忽略包装仿函数的返回类型,但仍然认为它是Callable,如果其他一切都匹配,而不是考虑它不Callable.
这个后C++ 14调整不影响这个特定的例子,因为所涉及的返回类型是一致的int
.