Overload resolution with std::function(使用 std::function 重载解析)
问题描述
考虑这个代码示例:
#include #include <功能>typedef std::functionfunc1_t;typedef std::functionfunc2_t;结构体{X (func1_t f){ }X (func2_t f){ }};内部主(){X x([](){ std::cout << "Hello, world!
"; });}
我确信它不应该编译,因为编译器不应该能够选择两个构造函数之一.g++-4.7.3 显示了这种预期行为:它表示重载构造函数的调用不明确.但是g++-4.8.2编译成功.
这段代码在 C++11 中是正确的还是这个版本的 g++ 的错误/特性?
In C++11...
我们来看看std::function
的构造函数模板的规范(它接受任何Callable):[func.wrap.func.con]/7-10
template函数(F f);模板<F类,A类>函数(allocator_arg_t,const A& a,F f);
7 要求: F
应该是 CopyConstructible
.对于参数类型 ArgTypes
和返回类型,f
应为 Callable
(20.10.11.2)R
.A
的拷贝构造函数和析构函数不得抛出例外.
8 后置条件: !*this
如果以下任何一项成立:
f
是一个NULL
函数指针.f
是指向成员的NULL
指针.F
是函数类模板的实例,!f
9 否则,*this
以std::move(f)
初始化的f
副本为目标.[这里省略了一个注释]
10 抛出:当 f
是函数指针或 reference_wrapper
时不应抛出异常,用于某些 T代码>.否则,可能会抛出
bad_alloc
或 F
的复制或移动构造函数抛出的任何异常.
现在,从 [](){}
构建或尝试构建(用于重载解析)std::function
(即带有签名void(void)
)违反了std::function
的构造函数的要求.
[res.on.required]/1
<块引用>违反函数的 Requires: 段落中指定的先决条件会导致未定义的行为,除非函数的 Throws: 段落指定在违反先决条件时抛出异常.>
所以,AFAIK,即使是重载决议的结果也是未定义的.因此,g++/libstdc++ 的两个版本都符合这一点.
<小时>在 C++14 中,这已更改,请参阅 LWG 2132.现在,std::function
的转换构造函数模板需要 SFINAE-reject 不兼容的 Callables(下一章将详细介绍 SFINAE):
template函数(F f);模板<F类,A类>函数(allocator_arg_t,const A& a,F f);
7 要求: F
应该是 CopyConstructible
.
8 备注: 这些构造函数不应参与重载解析,除非 f
对于参数类型是可调用的 (20.9.11.2)ArgTypes...
和返回类型 R
.
[...]
不应参与重载决议"对应于通过 SFINAE 的拒绝.最终效果是,如果您有一组重载函数 foo
,
void foo(std::function);void foo(std::function);
和一个调用表达式,例如
foo([](std::string){})//(C)
然后 foo
的第二个重载被明确选择:因为 std::function
将 F
定义为它的外部接口,F
定义了哪些参数类型被传递到 std::function
.然后,必须使用这些参数(参数类型)调用包装的函数对象.如果将 double
传递给 std::function
,则它不能传递给采用 std::string
的函数,因为没有转换 double
-> std::string
.对于 foo
的第一次重载,参数 [](std::string){}
因此不被视为可调用用于 std::function
.构造函数模板已停用,因此没有从 [](std::string){}
到 std::function
的可行转换.第一个重载从用于解析调用 (C) 的重载集中移除,只留下第二个重载.
请注意,由于 LWG 2420,上述措辞略有变化:有一个例外,如果 std::function
的返回类型 R
是 void
,则上述构造函数模板中的 Callable 的任何返回类型都将被接受(并丢弃).例如, []() ->void {}
和 []() ->bool {}
对于 std::function
是可调用的.因此,以下情况会产生歧义:
void foo(std::function);void foo(std::function);foo([]() -> bool {});//模糊的
重载解析规则不会尝试在不同的用户定义转换之间进行排名,因此 foo
的两个重载都是可行的(首先)并且两者都不是更好.
SFINAE 在这方面有什么帮助?
请注意,当 SFINAE 检查失败时,程序不是格式错误的,但该函数不适用于重载解析.例如:
#include #include 模板自动 foo(T) ->类型名称 std::enable_if自动 foo(T) ->类型名称 std::enable_if<不是 std::is_integral<T>::value >::type{ std::cout <<"foo 2
";}int main(){富(42);富(42.);}
同样,可以通过在转换构造函数上使用 SFINAE 使转换不可行:
#include #include 结构体{模板<类T,类=类型名称 std::enable_if
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!
"; });
}
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
"; }
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.);
}
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)
"; }
};
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.);
}
这篇关于使用 std::function 重载解析的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持编程学习网!
本文标题为:使用 std::function 重载解析
基础教程推荐
- Windows Media Foundation 录制音频 2021-01-01
- 从 std::cin 读取密码 2021-01-01
- 为 C/C++ 中的项目的 makefile 生成依赖项 2022-01-01
- 如何“在 Finder 中显示"或“在资源管理器中显 2021-01-01
- 如何在不破坏 vtbl 的情况下做相当于 memset(this, ...) 的操作? 2022-01-01
- 如何使图像调整大小以在 Qt 中缩放? 2021-01-01
- 管理共享内存应该分配多少内存?(助推) 2022-12-07
- 在 C++ 中循环遍历所有 Lua 全局变量 2021-01-01
- 使用从字符串中提取的参数调用函数 2022-01-01
- 为什么语句不能出现在命名空间范围内? 2021-01-01