lambdas的参数和返回值的类型转换规则是什么?

时间:2016-07-11 08:35:03

标签: c++ c++11 lambda type-conversion

我最近惊讶于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参数和返回值的类型转换的一般规则是什么?

4 个答案:

答案 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。对于参数类型fCallable应为ArgTypes(20.8.11.2),并返回类型R

其中Callable的定义如下:

  

f类型的可调用对象F对于参数类型CallableArgTypes,如果考虑表达式R,则返回类型INVOKE(f, declval<ArgTypes>()..., R)作为未评估的操作数(第5条),形成良好(20.8.2)。

反过来,

INVOKEdefined as

  
      
  1. 按如下方式定义INVOKE(f,t1,t2,...,tN):

         
        
    • ... 处理成员函数的情况 ...
    •   在所有其他情况下
    • f(t1, t2, ..., tN)
    •   
  2.   
  3. 如果INVOKE(f, t1, t2, ..., tN, R) cv static_cast<void>(INVOKE(f, t1, t2, ..., tN)),则R定义为void,否则INVOKE(f, t1, t2, ..., tN)会隐式转换为{{} 1}}。

  4.   

由于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>>;