我最近惊讶于lambdas可以分配给std::function
,其中略有不同的签名。 略有不同意味着当指定function
返回void
时,可能会忽略lambda的返回值,或者参数可能是function
中的引用但是lambda中的值。
请参阅this example (ideone),其中我强调了我怀疑不相容的内容。我认为返回值不是问题,因为你总是可以调用一个函数并忽略返回值,但是从引用到值的转换对我来说很奇怪:
int main() {
function<void(const int& i)> f;
// ^^^^ ^^^^^ ^
f = [](int i) -> int { cout<<i<<endl; return i; };
// ^^^ ^^^^^^
f(2);
return 0;
}
小问题是:为什么这段代码编译和工作?主要问题是:与std::function
一起使用时,lambda参数和返回值的类型转换的一般规则是什么?
答案 0 :(得分:16)
当该签名的lambda为assign a lambda to a function object时,您可以Lvalue-Callable类型为std::function<R(ArgTypes...)>
。反过来, Lvalue-Callable 是根据INVOKE operation来定义的,这意味着当lambda是一个左值时它必须是可调用的,并且它的所有参数都是所需类型的值和值类别(好像每个参数都是调用一个带有该参数类型作为其签名中的返回类型的nullary函数的结果)。
也就是说,如果你给你的lambda一个id,以便我们可以引用它的类型,
auto l = [](int i) -> int { cout<<i<<endl; return i; };
将其分配给function<void(const int&)>
,表达式
static_cast<void>(std::declval<decltype(l)&>()(std::declval<const int&>()))
必须格式良好。
std::declval<const int&>()
的结果是对const int
的左值引用,但将int
参数绑定没有问题,因为这只是lvalue-to-rvalue conversion,这是return l(static_cast<int const&>(int{}));
considered an exact match用于重载解析的目的:
SELECT T1.[Employee ID]
,T1.Surname
,T2.EmpID
T2.Surname1
FROM Table1 T1 INNER JOIN Table2 T2 ON T1.[Employee ID] = T2.EmpID AND
T1.Surname <> T2.Surname1
ORDER BY T1.[Employee ID]
正如您所观察到的,返回值被丢弃if the function object signature has return type void
;否则,返回类型必须是可隐式转换的。正如乔纳森·威克利所指出的那样,C ++ 11在这方面的表现令人不满(Using `std::function<void(...)>` to call non-void function; Is it illegal to invoke a std::function<void(Args...)> under the standard?),但在LWG 2420中已经修复了。该决议适用于C ++ 14的出版后修复。大多数现代C ++编译器都会提供C ++ 14行为(经过修改)作为扩展,即使在C ++ 11模式下也是如此。
答案 1 :(得分:4)
赋值赋值运算符被定义为具有以下效果:
function(std::forward<F>(f)).swap(*this);
( 14882:2011 20.8.11.2.1 par.18 )
此引用的构造函数
template <class F> function(F f);
需要:
F
应为CopyConstructible
。对于参数类型f
,Callable
应为ArgTypes
(20.8.11.2),并返回类型R
。
其中Callable
的定义如下:
反过来,
f
类型的可调用对象F
对于参数类型Callable
为ArgTypes
,如果考虑表达式R
,则返回类型INVOKE(f, declval<ArgTypes>()..., R)
作为未评估的操作数(第5条),形成良好(20.8.2)。
INVOKE
是defined as:
按如下方式定义INVOKE(f,t1,t2,...,tN):
- ... 处理成员函数的情况 ...
在所有其他情况下f(t1, t2, ..., tN)
。- 醇>
如果
INVOKE(f, t1, t2, ..., tN, R)
cvstatic_cast<void>(INVOKE(f, t1, t2, ..., tN))
,则R
定义为void
,否则INVOKE(f, t1, t2, ..., tN)
会隐式转换为{{} 1}}。
由于R
定义变为普通函数调用,在这种情况下,参数可以是converted:如果您的INVOKE
接受std::function<void(const int&)>
,那么它可以被转换致电const int&
。以下示例使用clang++ -std=c++14 -stdlib=libc++ -Wall -Wconversion
编译:
int
返回类型(int main() {
std::function<void(const int& i)> f;
f = [](int i) -> void { std::cout << i << std::endl; };
f(2);
return 0;
}
)由void
定义的static_cast<void>
特殊情况处理。
但请注意,在编写本文时,使用clang++ -std=c++1z -stdlib=libc++ -Wconversion -Wall
编译时会产生错误,但在使用clang++ -std=c++1z -stdlib=libstdc++ -Wconversion -Wall
编译时则不会产生错误:
INVOKE
这是由于int main() {
std::function<void(const int& i)> f;
f = [](int i) -> int { std::cout << i << std::endl; return i;};
f(2);
return 0;
}
实现了C ++ 14中指定的行为,而不是上面描述的amended behaviour(感谢@Jonathan Wakely指出这一点)。 @Arunmu在his post中描述了libc++
类型特征对同一事物负责。在这方面,在处理具有libstdc++
返回类型的callables时,实现可能会略有不同,具体取决于它们是否实现了C ++ 11,14或更新的东西。
答案 2 :(得分:4)
从引用到值的转换对我来说很奇怪
为什么?
这看起来也很奇怪吗?
int foo(int i) { return i; }
void bar(const int& ir) { foo(ir); }
这完全一样。按值int
的函数由另一个函数调用,通过const-reference获取int
。
在bar
内,变量ir
被复制,返回值被忽略。这正是std::function<void(const int&)>
内部具有签名int(int)
的目标时发生的情况。
答案 3 :(得分:3)
只是添加到@ecatmur回答,g ++ / libstd ++只是选择忽略callable
的返回值,它就像忽略常规代码中的返回值一样正常:
static void
_M_invoke(const _Any_data& __functor, _ArgTypes&&... __args)
{
(*_Base::_M_get_pointer(__functor))( // Gets the pointer to the callable
std::forward<_ArgTypes>(__args)...);
}
在libstd ++中明确允许这种情况的类型特征是:
template<typename _From, typename _To>
using __check_func_return_type = __or_<is_void<_To>, is_convertible<_From, _To>>;