std::function 的模板参数(签名)不是其类型的一部分吗?

发布于 2024-11-06 00:19:36 字数 803 浏览 0 评论 0原文

鉴于以下代码,歧义背后的原因是什么?我可以绕过它还是必须保留(烦人的)显式强制转换?

#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 参数注释掉 a() 函数,并在我的main 中,由于 x 与唯一 a() 的参数 function 之间的类型不匹配,编译正确失败> 功能可用。如果编译器在这种情况下失败,为什么当两个 a() 函数存在时会出现歧义?

我尝试过 VS2010 和 g++ v.4.5。两者都给了我完全相同的模糊性。

Given the following code, what is the reason behind the ambiguity? Can I circumvent it or will I have to keep the (annoying) explicit casts?

#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;
}

Interestingly, if I comment out the a() function with the function<int ()> parameter and call a(x) in my main, the compilation correctly fails because of the type mismatch between x and the argument function<int (int)> of the only a() function available. If the compiler fails in that case, why would there be any ambiguity when the two a() functions are present?

I've tried with VS2010 and g++ v. 4.5. Both give me the exact same ambiguity.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

万人眼中万个我 2024-11-13 00:19:36

问题是 functionfunction 都可以从同一个函数构造。这就是 VS2010 中 std::function 的构造函数声明的样子:

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。为了使其可行,需要进行转换。要将 int(*)() 转换为 myfunc,它会尝试 myfunc 的构造函数。作为一个可以接受任何东西的模板,转换自然会成功。
现在它尝试对第二次过载进行相同的操作。构造函数仍然相同,并且仍然接受给定的任何内容,转换也有效。
由于重载集中只剩下 2 个函数,编译器就像一只悲伤的熊猫,不知道该怎么做,所以它只是说调用是不明确的。


所以最后,模板的 Signature 部分在进行声明/定义时确实属于该类型,但在您想要构造对象时则不属于该类型。


编辑
我全神贯注于回答标题问题,完全忘记了你的第二个问题。 :(

我可以绕过它还是必须保留(烦人的)显式强制转换?

Afaik,你有 3 个选择。

  • 保持强制转换
  • 创建适当类型的函数对象并传递该对象

    函数fx = x;
    函数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);
};

当然,这需要扩展您想要支持的参数数量,但这只需完成一次,然后埋在 中“get_signature.h”标头。 :)

我考虑过但立即放弃的另一个选择是 SFINAE,它会引入比 TMP 版本更多的样板代码。

所以,是的,这就是我所知道的选项。希望其中之一适合您。 :)

The problem is that both function<int()> and function<int(int)> are constructible from the same function. This is what the constructor declaration of std::function looks like in VS2010:

template<class _Fx>
function(_Fx _Func, typename _Not_integral<!_Is_integral<_Fx>::value, int>::_Type = 0);

Ignoring the SFINAE part, it is constructible from pretty much anything.
std::/boost::function employ a technique called type erasure, to allow arbitary objects/functions to be passed in, so long they satisfy the signature when being called. One drawback from that is, that you get an error in the deepest part of the implementation (where the saved function is being called) when supplying an object which can't be called like the signature wants it to, instead of in the constructor.


The problem can be illustrated with this little class:

template<class Signature>
class myfunc{
public:
    template<class Func>
    myfunc(Func a_func){
        // ...
    }
};

Now, when the compiler searches for valid functions for the overload set, it tries to convert the arguments if no perfect fitting function exists. The conversion can happen through the constructor of the parameter of the function, or through a conversion operator of the argument given to the function. In our case, it's the former.
The compiler tries the first overload of a. To make it viable, it needs to make a conversion. To convert a int(*)() to a myfunc<int()>, it tries the constructor of myfunc. Being a template that takes anything, the conversion naturally succeeds.
Now it tries the same with the second overload. The constructor still being the same and still taking anything given to it, the conversion works too.
Being left with 2 functions in the overload set, the compiler is a sad panda and doesn't know what to do, so it simply says the call is ambigious.


So in the end, the Signature part of the template does belong to the type when making declarations/definitions, but doesn't when you want to construct an object.


Edit:
With all my attention on answering the title-question, I totally forgot about your second question. :(

Can I circumvent it or will I have to keep the (annoying) explicit casts?

Afaik, you have 3 options.

  • Keep the cast
  • Make a function object of the appropriate type and pass that

    function<int()> fx = x;
    function<int(int)> fy = y;
    a(fx);
    a(fy);

  • Hide the tedious casting in a function and use TMP to get the right signature

The TMP (template metaprogramming) version is quite verbose and with boilerplate code, but it hides the casting from the client. An example version can be found here, which relies on the get_signature metafunction that is partially specialized on function pointer types (and provides a nice example how pattern matching can work in 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);
};

Of course, this needs to be extended for the number of arguments you want to support, but that is done once and then buried in a "get_signature.h" header. :)

Another option I consider but immediatly discarded was SFINAE, which would introduce even more boilerplate code than the TMP version.

So, yeah, that are the options that I know of. Hope one of them works for you. :)

心如狂蝶 2024-11-13 00:19:36

我已经看到这个问题多次出现了。 libc++ 现在可以毫无歧义地编译此代码(作为一致的扩展)。

逾期更新

事实证明,这个“扩展”非常受欢迎,以至于它在 C++14 中被标准化(尽管我个人并不负责完成这项工作)。

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

此后 C++14 调整不会影响此特定示例,因为涉及的返回类型始终为 int

I've seen this question come up one too many times. libc++ now compiles this code without ambiguity (as a conforming extension).

Overdue Update

This "extension" proved sufficiently popular that it was standardized in C++14 (though I was not personally responsible for getting that job done).

In hindsight, I did not get this extension exactly correct. Earlier this month (2015-05-09) the committee voted in LWG issue 2420 which effectively changes the definition of Callable so that if the std::function has a void return type it will ignore the return type of the wrapped functor, but still otherwise consider it Callable if everything else matches up, instead of considering it not Callable.

This post-C++14 tweak does not impact this particular example since the return types involved are consistently int.

何其悲哀 2024-11-13 00:19:36

下面是如何将 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);
}

请注意,这与函数签名上的重载并不完全相同,因为它将可转换参数(和返回)类型视为等效类型。对于精确的重载,这应该有效:

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)) { }
};

Here's an example of how to wrap std::function in a class that checks invokability of its constructor parameters:

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)) { }
};

Use like this:

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);
}

Note that this isn't quite the same as overloading on function signature, as it treats convertible argument (and return) types as equivalent. For exact overloading, this should work:

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)) { }
};
看透却不说透 2024-11-13 00:19:36

std::function 有一个采用任意类型(即除 T 之外的类型)的转换构造函数。当然,在这种情况下,该 ctor 会导致类型不匹配错误,但编译器不会走到这一步——调用是不明确的,仅仅因为该 ctor 存在。

std::function<T> has a conversion ctor that takes an arbitrary type (i.e., something other than a T). Sure, in this case, that ctor would result in a type mismatch error, but the compiler doesn't get that far -- the call is ambiguous simply because the ctor exists.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文