是不是types的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; }
有趣的是,如果我使用function<int ()>
注释掉a()
函数并在main中调用a(x)
,编译器会正确地失败,因为x
和参数function<int (int)>
可用的唯一的a()
函数。 如果编译器在这种情况下失败了,那么为什么当两个a()
函数存在时会有不明确的地方?
我已经尝试过VS2010和g ++ v。4.5。 两者都给我一模一样的含糊不清。
问题是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部分,它几乎可以构build任何东西。
std::/boost::function
使用了一种称为types擦除的技术,允许任意的对象/函数被传入,只要它们被调用时就能满足签名。 这样做的一个缺点是,当提供一个不能像签名所期望的那样的对象而不是在构造函数中时,你会在实现的最深处(在调用保存的函数时)得到一个错误。
这个小class可以说明问题:
template<class Signature> class myfunc{ public: template<class Func> myfunc(Func a_func){ // ... } };
现在,当编译器为重载集合search有效函数时,如果没有完美拟合函数存在,它会尝试转换参数。 转换可以通过函数的参数的构造函数来实现,也可以通过赋给函数的参数的转换运算符来实现。 就我们而言,这是前者。
编译器会尝试第一个重载。 为了使其可行,需要进行转换。 要将int(*)()
转换为myfunc<int()>
,它会尝试myfunc
的构造函数。 作为一个需要任何东西的模板,转换自然成功。
现在它和第二次重载一样。 构造函数仍然是相同的,仍然采取任何东西,转换也起作用。
编译器在超载集中有2个函数,是一只可悲的pandas,不知道该怎么做,只是说这个调用是有意义的。
所以最后,模板的Signature
部分在进行声明/定义的时候属于types,但是当你想要构造一个对象的时候不会。
编辑 :
我全神贯注地回答题目问题,完全忘记了你的第二个问题。 🙁
我可以规避吗?还是我必须保持(烦人的)明确的演员?
Afaik,你有3个选项。
- 保持演员阵容
-
创build一个适当types的
function
对象并传递它function<int()> fx = x; function<int(int)> fy = y; a(fx); a(fy);
-
在函数中隐藏繁琐的转换,并使用TMP来获得正确的签名
TMP(模板元编程)版本相当详细,并带有样板代码,但它隐藏了来自客户端的投射。 一个示例版本可以在这里find,它依赖于部分专用于函数指针types的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"
头文件中。 🙂
我考虑的另一个select是立即废弃,SFINAE会比TMP版本引入更多的样板代码。
所以,是的,那是我所知道的选项。 希望其中一个为你工作。 🙂
我看过这个问题太多了。 libc ++现在编译这个代码而没有歧义(作为一个符合的扩展)。
逾期更新
这个“扩展”被certificate是非常stream行的,它在C ++ 14中被标准化(尽pipe我没有亲自负责完成这个工作)。
事后看来,我没有得到这个扩展完全正确。 本月早些时候(2015-05-09),委员会在LWG issue 2420中进行了投票,这有效地改变了Callable的定义,所以如果std::function
有一个void
返回types,它将忽略被包装的函子的返回types,但仍然否则认为它可以调用,如果一切都匹配,而不是考虑它不可Callable 。
这后C + + 14的调整不会影响这个特定的例子,因为所涉及的返回types是一致的int
。
下面是一个如何在检查构造函数参数的可调用性的类中包装std::function
的例子:
template<typename> struct check_function; template<typename R, typename... Args> struct check_function<R(Args...)>: public std::function<R(Args...)> { template<typename T, class = typename std::enable_if< std::is_same<R, void>::value || std::is_convertible< decltype(std::declval<T>()(std::declval<Args>()...)), R>::value>::type> check_function(T &&t): std::function<R(Args...)>(std::forward<T>(t)) { } };
像这样使用:
int a(check_function<int ()> f) { return f(); } int a(check_function<int (int)> f) { return f(0); } int x() { return 22; } int y(int) { return 44; } int main() { a(x); a(y); }
请注意,这与函数签名上的重载不完全相同,因为它将可转换参数(和返回)types视为等价的。 对于确切的重载,这应该工作:
template<typename> struct check_function_exact; template<typename R, typename... Args> struct check_function_exact<R(Args...)>: public std::function<R(Args...)> { template<typename T, class = typename std::enable_if< std::is_convertible<T, R(*)(Args...)>::value>::type> check_function_exact(T &&t): std::function<R(Args...)>(std::forward<T>(t)) { } };
std::function<T>
有一个转换ctor,它接受任意types(即T
以外的东西)。 当然,在这种情况下,ctor会导致types不匹配的错误,但是编译器不会得到那么多的 – 由于ctor存在,调用是不明确的。