std :: function的模板参数(签名)是不是它的类型?

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中调用,则编译正确失败,因为它们之间的类型不匹配xfunction<int (int)>唯一a()可用函数的参数.如果编译器在这种情况下失败,为什么在存在这两个a()函数时会有任何歧义?

我试过VS2010和g ++ v.4.5.两者都给我完全相同的歧义.

Xeo*_*Xeo 40

问题是,无论function<int()>function<int(int)>来自同一个函数构造的.这是std::functionVS2010中构造函数声明的样子:

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版本更多的样板代码.

所以,是的,这是我所知道的选择.希望其中一个适合你.:)

  • @nightcracker:您是否想过您的问题可能是C++是OOP语言的假设?我没有看到你如何批评C++在你要求它做某事时过于冗长*在其他语言中根本不可能*.如果其他语言允许你使用SFINAE,那么你肯定可以说C++的版本太冗长了. (12认同)
  • @nightcracker:呃......你讨厌C++,因为它太强大了?:X (11认同)
  • @nightcracker:TBH,我知道没有别的方法来批评C++,但我仍然讨厌你这样做的方式. (6认同)
  • 这就是我讨厌C++的原因. (4认同)
  • @jalf:我从来没有说过C++是一种OOP语言,我批评了C++实现OOP的方式.可悲的是,除了C++之外,我所知道的其他任何语言都没有.TBH,我们说话时我不会知道更好的方式.我所知道的是,我真的不喜欢C++的做法.此外,我不仅发现冗长是一个问题(冗长是好的,通常),还有语义.举个例子,`get_signature`结构对我来说没有任何语义含义 - 这对我来说是一个黑客攻击.但是在这里讨论这个对任何人都没有帮助,如果你真的想回复这个,建议聊聊,我会尝试加入. (2认同)

How*_*ant 11

我已经看过这个问题太多次了. libc ++现在编译此代码时没有歧义(作为符合标准的扩展).

逾期更新

这种"扩展"证明非常受欢迎,它在C++ 14中被标准化(尽管我个人不负责完成这项工作).

事后来看,我没有得到这个扩展完全正确.本月早些时候(2015-05-09)委员会在LWG问题2420中投票,它有效地改变了Callable的定义,因此如果std::function有一个void返回类型,它将忽略包装仿函数的返回类型,但仍然认为它是Callable,如果其他一切都匹配,而不是考虑它不Callable.

这个后C++ 14调整不影响这个特定的例子,因为所涉及的返回类型是一致的int.