使用std :: function的过载解析 [英] Overload resolution with std::function
问题描述
请考虑此代码示例:
#include< iostream>
#include< functional>
typedef std :: function< void()> func1_t;
typedef std :: function< void(int)> func2_t;
struct X
{
X(func1_t f)
{}
X(func2_t f)
{}
};
int main()
{
X x([](){std :: cout<<Hello,world!\\\
;});
}
我确信它不应该编译,因为编译器不应该能够选择两个构造函数之一。 g ++ - 4.7.3显示了这个预期的行为:它说,重载的构造函数的调用是不明确的。但是,g ++ - 4.8.2成功编译它。
这个代码在C ++ 11中是否正确,或者是这个版本的g ++的错误/ p>
在C ++ 11 ...
在 std :: function
(它接受任何Callable)的构造函数模板的规范:[func.wrap.func.con] / 7-10
template< class F>函数(F f);
template< class F,class A>函数(allocator_arg_t,const A& a,F f);
7 需要:
F
应为CopyConstructible
。f
应为可调用
(20.10.11.2)的参数类型ArgTypes
并返回类型
R
。A
的复制构造函数和析构函数不得引发
异常。
8 后置条件:
在
- code> f 是
NULL
函数指针。
f
是指向成员的NULL
指针。
F
是函数类模板的一个实例,!f
< 9否则,* this
定位用std :: move(f)初始化的
。f
)f
是T
的函数指针或reference_wrapper< T>
否则,可能抛出
bad_alloc
或者由F
的复制或移动构造函数抛出的任何异常。 p>
现在,构造或试图构建一个 std :: function< void(int) < code> void(void)
从 [](){}
>)违反 std :: function< void(int)>
的构造函数的要求。
res.on.required] / 1
违反函数中要求的前提条件:未定义的行为,除非函数的 Throws:段落指定在违反前提条件时抛出异常。
AFAIK,即使是重载分辨率的结果是未定义的。因此,这两个版本的g ++ / libstdc ++都符合这一方面。
在C ++ 14中, ,请参阅 LWG 2132 。现在, std :: function
的转换构造函数模板是SFINAE拒绝不兼容的Callables所必需的(更多关于下一章的SFINAE):
模板< class F>函数(F f);
template< class F,class A>函数(allocator_arg_t,const A& a,F f);
7 需要:
F
应为CopyConstructible
。
8 备注:在过载
解析,除非f
是Callable(20.9.11.2)的参数类型
ArgTypes ...
和返回类型R
。
不参与重载解析对应于通过SFINAE的拒绝。净效果是,如果你有一个重载集合的函数 foo
,
void foo(std :: function< void(double)>);
void foo(std :: function< void(char const *)>);
和一个调用表达式,例如
foo([](std :: string){})//(C)
那么 foo
的第二个重载是明确选择的:因为 std :: function< F>
定义 F
作为其外部接口, F
定义哪些参数类型传递到 std :: function
。然后,必须使用这些参数(参数类型)调用包装的函数对象。如果 double
被传递到 std :: function
,它不能被传递到一个函数, $ c> std :: string ,因为没有转换 double
- > std :: string
。
对于 foo
的第一个重载, [](std :: string){}
因此不被认为是 std :: function< void(double)>
的Callable。构造函数模板被停用,因此没有可行的转换从 [](std :: string){}
到 std :: function< void )>
。这个第一个过载被从用于解决调用的过载集合(C)中去除,只留下第二个过载。
请注意,由于 LWG 2420 :有一个例外,如果返回类型<$ std :: function< R(ArgTypes ...)>
的 []() - > void {}
和 []() - > bool {}
是可调用 std :: function< void()>
。因此,以下情况产生模糊性:
void foo(std :: function< void()>);
void foo(std :: function< bool()>);
foo([]() - > bool {}); // ambiguous
重载解析规则不会尝试在不同的用户定义转化中排名,因此, foo
的两个重载都是可行的(首先),也不是更好。
SFINAE如何帮助这里?
注意,当SFINAE检查失败时,程序没有格式化,但函数不是可用于重载分辨率。例如:
#include< type_traits>
#include< iostream>
template< class T>
auto foo(T) - > typename std :: enable_if< std :: is_integral< T> :: value> :: type
{std :: cout< foo 1 \\\
; }
template< class T>
auto foo(T) - > typename std :: enable_if< not std :: is_integral< T> :: value> :: type
{std :: cout< foo 2 \\\
; }
int main()
{
foo(42);
foo(42。);
}
同样,使用SFINAE可以使转换不可行构造函数:
#include< type_traits>
#include< iostream>
struct foo
{
template< class T,class =
typename std :: enable_if< std :: is_integral< T> :: value> :: type>
foo(T)
{std :: cout< foo(T)\\\
; }
};
struct bar
{
template< class T,class =
typename std :: enable_if< not std :: is_integral< T> :: value> :: type>
bar(T)
{std :: cout< bar(T)\\\
; }
};
struct kitty
{
kitty(foo){}
kitty(bar){}
};
int main()
{
kitty cat(42);
kitty tac(42。);
}
Consider this example of code:
#include <iostream>
#include <functional>
typedef std::function<void()> func1_t;
typedef std::function<void(int)> func2_t;
struct X
{
X (func1_t f)
{ }
X (func2_t f)
{ }
};
int main ( )
{
X x([](){ std::cout << "Hello, world!\n"; });
}
I was sure that it shouldn't compile, because the compiler shouldn't be able to choose one of the two constructors. g++-4.7.3 shows this expected behavior: it says that call of overloaded constructor is ambiguous. However, g++-4.8.2 successfully compiles it.
Is this code correct in C++11 or it is a bug/feature of this version of g++?
In C++11...
Let's take a look at the specification of the constructor template of std::function
(which takes any Callable): [func.wrap.func.con]/7-10
template<class F> function(F f); template <class F, class A> function(allocator_arg_t, const A& a, F f);
7 Requires:
F
shall beCopyConstructible
.f
shall beCallable
(20.10.11.2) for argument typesArgTypes
and return typeR
. The copy constructor and destructor ofA
shall not throw exceptions.8 Postconditions:
!*this
if any of the following hold:
f
is aNULL
function pointer.f
is aNULL
pointer to member.F
is an instance of the function class template, and!f
9 Otherwise,
*this
targets a copy off
initialized withstd::move(f)
. [left out a note here]10 Throws: shall not throw exceptions when
f
is a function pointer or areference_wrapper<T>
for someT
. Otherwise, may throwbad_alloc
or any exception thrown byF
’s copy or move constructor.
Now, constructing, or attempting to construct (for overload resolution) a std::function<void(int)>
from a [](){}
(i.e. with signature void(void)
) violates the requirements of std::function<void(int)>
's constructor.
[res.on.required]/1
Violation of the preconditions specified in a function’s Requires: paragraph results in undefined behavior unless the function’s Throws: paragraph specifies throwing an exception when the precondition is violated.
So, AFAIK, even the result of the overload resolution is undefined. Therefore, both versions of g++/libstdc++ are complying in this aspect.
In C++14, this has been changed, see LWG 2132. Now, the converting constructor template of std::function
is required to SFINAE-reject incompatible Callables (more about SFINAE in the next chapter):
template<class F> function(F f); template <class F, class A> function(allocator_arg_t, const A& a, F f);
7 Requires:
F
shall beCopyConstructible
.8 Remarks: These constructors shall not participate in overload resolution unless
f
is Callable (20.9.11.2) for argument typesArgTypes...
and return typeR
.[...]
The "shall not participate in overload resolution" corresponds to rejection via SFINAE. The net effect is that if you have an overload set of functions foo
,
void foo(std::function<void(double)>);
void foo(std::function<void(char const*)>);
and a call-expression such as
foo([](std::string){}) // (C)
then the second overload of foo
is chosen unambiguously: Since std::function<F>
defines F
as its interface to the outside, the F
defines which argument types are passed into std::function
. Then, the wrapped function object has to be called with those arguments (argument types). If a double
is passed into std::function
, it cannot be passed on to a function taking a std::string
, because there's no conversion double
-> std::string
.
For the first overload of foo
, the argument [](std::string){}
is therefore not considered Callable for std::function<void(double)>
. The constructor template is deactivated, hence there's no viable conversion from [](std::string){}
to std::function<void(double)>
. This first overload is removed from the overload set for resolving the call (C), leaving only the second overload.
Note that there's been a slight change to the wording above, due to LWG 2420: There's an exception that if the return type R
of a std::function<R(ArgTypes...)>
is void
, then any return type is accepted (and discarded) for the Callable in the constructor template mentioned above. For example, both []() -> void {}
and []() -> bool {}
are Callable for std::function<void()>
. The following situation therefore produces an ambiguity:
void foo(std::function<void()>);
void foo(std::function<bool()>);
foo([]() -> bool {}); // ambiguous
The overload resolution rules don't try to rank among different user-defined conversions, and hence both overloads of foo
are viable (first of all) and neither is better.
How can SFINAE help here?
Note when a SFINAE-check fails, the program isn't ill-formed, but the function isn't viable for overload resolution. For example:
#include <type_traits>
#include <iostream>
template<class T>
auto foo(T) -> typename std::enable_if< std::is_integral<T>::value >::type
{ std::cout << "foo 1\n"; }
template<class T>
auto foo(T) -> typename std::enable_if< not std::is_integral<T>::value >::type
{ std::cout << "foo 2\n"; }
int main()
{
foo(42);
foo(42.);
}
Similarly, a conversion can be made non-viable by using SFINAE on the converting constructor:
#include <type_traits>
#include <iostream>
struct foo
{
template<class T, class =
typename std::enable_if< std::is_integral<T>::value >::type >
foo(T)
{ std::cout << "foo(T)\n"; }
};
struct bar
{
template<class T, class =
typename std::enable_if< not std::is_integral<T>::value >::type >
bar(T)
{ std::cout << "bar(T)\n"; }
};
struct kitty
{
kitty(foo) {}
kitty(bar) {}
};
int main()
{
kitty cat(42);
kitty tac(42.);
}
这篇关于使用std :: function的过载解析的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!