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

时间:2022-11-07 16:29:03

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?

有趣的是,如果我用函数 参数注释掉a()函数,并在main中调用a(x),那么编译就会失败,因为x和参数函数 之间的类型不匹配,而a()函数是唯一可用的a()函数。如果编译器在这种情况下失败了,那么当两个a()函数存在时,为什么会有歧义呢? ()>

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

我尝试过VS2010和g++ v4.5。两者都给了我同样的模糊性。

4 个解决方案

#1


39  

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:

问题是函数 和函数 都是由同一个函数构造的。这是std:::函数在VS2010中的构造函数声明: (int)> ()>

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.

忽略了SFINAE部分,它可以从几乎任何东西中构造出来。boost:::函数采用了一种名为“类型删除”的技术,允许将任意的对象/函数传递进来,只要被调用时,它们就能满足签名。这样做的一个缺点是,当提供一个不能像签名希望的那样调用的对象而不是在构造函数中调用的对象时,在实现的最深层部分(调用保存的函数)会出现错误。


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.

现在,当编译器为重载集搜索有效函数时,如果不存在完美的拟合函数,它将尝试转换参数。转换可以通过函数参数的构造函数进行,也可以通过对函数的参数的转换操作符来实现。在我们的例子中,它是前者。编译器尝试a的第一个重载,为了使它可行,它需要进行转换。要将int(*)()转换为func ,它将尝试myfunc的构造函数。作为一个需要任何东西的模板,转换自然会成功。现在它又尝试了第二个重载。构造函数仍然是相同的,并且仍然接受给定的任何东西,转换也可以工作。在重载集中只剩下两个函数,编译器是一个悲伤的熊猫,不知道该怎么做,所以它只说调用是矛盾的。 ()>


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

    < int()函数> fx = x;< int(int)>年度= y的函数;(外汇);(年度);

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

    在函数中隐藏冗长的强制转换,并使用TMP获得正确的签名

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++):

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

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. :)

当然,这需要扩展到您想要支持的参数的数量,但这是一次性完成的,然后被隐藏在“get_signature”中。h”头。:)

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

另一个我考虑但立即被抛弃的选项是SFINAE,它将引入比TMP版本更多的样板代码。

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

这就是我所知道的选项。希望他们中的一个对你有用。:)

#2


10  

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

我看到这个问题被提过很多次。现在,libc++编译这段代码时没有歧义(作为符合标准的扩展)。

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).

这个“扩展”被证明是非常流行的,它在c++ 14中被标准化了(尽管我个人并不负责完成这项工作)。

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.

事后看来,我并没有得到完全正确的扩展。本月早些时候(2015-05-09)2420年委员会投票问题库工作小组将会忠实地有效地改变的定义可调用,如果std::函数有一个void返回类型,它将忽略包函子的返回类型,但仍然否则考虑调用如果一切匹配,而不是考虑没有可调用的。

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

这个后c++ 14调整不会影响这个特定示例,因为涉及的返回类型始终是int型的。

#3


4  

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

下面是如何在类中封装std::函数的示例,该类检查其构造函数参数的可调用性:

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

#4


2  

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.

函数 有一个转换ctor,它接受任意类型(例如。当然,在这种情况下,ctor会导致类型不匹配错误,但是编译器不会得到那么远的结果——调用是不明确的,因为ctor存在。

#1


39  

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:

问题是函数 和函数 都是由同一个函数构造的。这是std:::函数在VS2010中的构造函数声明: (int)> ()>

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.

忽略了SFINAE部分,它可以从几乎任何东西中构造出来。boost:::函数采用了一种名为“类型删除”的技术,允许将任意的对象/函数传递进来,只要被调用时,它们就能满足签名。这样做的一个缺点是,当提供一个不能像签名希望的那样调用的对象而不是在构造函数中调用的对象时,在实现的最深层部分(调用保存的函数)会出现错误。


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.

现在,当编译器为重载集搜索有效函数时,如果不存在完美的拟合函数,它将尝试转换参数。转换可以通过函数参数的构造函数进行,也可以通过对函数的参数的转换操作符来实现。在我们的例子中,它是前者。编译器尝试a的第一个重载,为了使它可行,它需要进行转换。要将int(*)()转换为func ,它将尝试myfunc的构造函数。作为一个需要任何东西的模板,转换自然会成功。现在它又尝试了第二个重载。构造函数仍然是相同的,并且仍然接受给定的任何东西,转换也可以工作。在重载集中只剩下两个函数,编译器是一个悲伤的熊猫,不知道该怎么做,所以它只说调用是矛盾的。 ()>


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

    < int()函数> fx = x;< int(int)>年度= y的函数;(外汇);(年度);

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

    在函数中隐藏冗长的强制转换,并使用TMP获得正确的签名

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++):

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

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. :)

当然,这需要扩展到您想要支持的参数的数量,但这是一次性完成的,然后被隐藏在“get_signature”中。h”头。:)

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

另一个我考虑但立即被抛弃的选项是SFINAE,它将引入比TMP版本更多的样板代码。

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

这就是我所知道的选项。希望他们中的一个对你有用。:)

#2


10  

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

我看到这个问题被提过很多次。现在,libc++编译这段代码时没有歧义(作为符合标准的扩展)。

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).

这个“扩展”被证明是非常流行的,它在c++ 14中被标准化了(尽管我个人并不负责完成这项工作)。

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.

事后看来,我并没有得到完全正确的扩展。本月早些时候(2015-05-09)2420年委员会投票问题库工作小组将会忠实地有效地改变的定义可调用,如果std::函数有一个void返回类型,它将忽略包函子的返回类型,但仍然否则考虑调用如果一切匹配,而不是考虑没有可调用的。

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

这个后c++ 14调整不会影响这个特定示例,因为涉及的返回类型始终是int型的。

#3


4  

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

下面是如何在类中封装std::函数的示例,该类检查其构造函数参数的可调用性:

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

#4


2  

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.

函数 有一个转换ctor,它接受任意类型(例如。当然,在这种情况下,ctor会导致类型不匹配错误,但是编译器不会得到那么远的结果——调用是不明确的,因为ctor存在。