在std :: function operator()和std :: forward中发生了什么?

时间:2017-03-05 15:08:41

标签: c++ c++11 c++14 std-function perfect-forwarding

我正在查看std::function实施及其调用operator()

template<typename Ret, typename... ArgTypes>
Ret function< Ret (ArgTypes...)>::operator()(ArgTypes...args) const
{
  // some stuff
  return invoker(functor, std::forward<ArgTypes>(args)...);
}

我特别疑惑,为什么在这里使用std::forward?这有什么与完美转发有关吗? 因为只有当operator()是具有可变参数模板声明template<typename... Args>的模板(它不是,声明是std :: function 的部分特化)时才能完成转发。 )。 在这里使用std :: forward的意图是什么?我很困惑: - )?

3 个答案:

答案 0 :(得分:4)

这是正确的,这不是典型的“完美转发”方案。一个简短的例子可以帮助说明动机。假设类型A具有检测构造函数和析构函数:

#include "A.h"
#include <functional>
#include <iostream>

int
main()
{
    A a1{1};
    A a2{2};
    std::function<void(A, A&)> f{[](A x, A& y){}};
    f(a1, a2);
}

这将输出:

A(int state): 1
A(int state): 2
A(A const& a): 1
A(A&& a): 1
~A(1)
~A(-1)
~A(2)
~A(1)

说明:

a1a2构建在堆栈上。然后,当传递到function调用者时,首先复制a1以绑定到第一个按值参数,然后在{em>移动的std::forward<A>上调用a1 它从by-value参数到lambda。

相反,无需复制a2以绑定function A&参数,然后调用std::forward<A&>(a2),将a2转发为A& lvalue而不是rvalue,这绑定到lambda的~A(-1)参数。

然后事情就会被破坏。 A表示使用此已检测的A在移动构建状态中销毁ArgTypes

总而言之,即使ArgTypes没有像通常的完美转发习惯用法那样推断,我们仍然希望将按值ArgTypes转发为rvalues,并按引用std::forward转发作为左值。所以 $ declare @result AS varchar(50) DECLARE @peopleId AS varchar(50) if('232332' NOT LIKE '%[^0-9]%') BEGIN SET @result='Numeric' PRINT @result END ELSE BEGIN set @result='nonNumeric' print @result END select isnull(class.grade,'') as grade,ISNULL(class.room,'') as room,student.prefix as prefix,student.student_id as student_id,(person.first_name+' '+person.last_name) as name, person.dob as dob,person.people_id as people_id,quit_on, case when student.student_status='30' then N'พักการเรียน' when student.student_status='31' then N'น.ร.ไปเรียนโครงการฯ' else '' end as quit_reason from school_student student inner join people_person person on student.person_id=person.id left join school_classroom_students classStudent on classStudent.student_id=student.id left join school_classroom class on class.id =classStudent.classroom_id where student.student_status in('30','31') and student.system_status = 'DC' and student.school_id=@schoolId AND case WHEN @result='nonNumeric' then-- this should execure person.people_id=@peopleId else---- this should work person.first_name+' '+ person.last_name LIKE '%'+@peopleId+'%' 碰巧恰好做了我们想要的。

答案 1 :(得分:1)

我觉得你对这里的很多事感到困惑。

首先,完美转发与可变参数模板无关。您可以创建一个包装类,该类包含一个函数,该函数接受一个参数并将其转发给包装对象:

template<typename T>
struct Wrapper {
    template<typename Arg>
    decltype(auto) test(Arg&& arg) {
        return t.test(std::forward<Arg>(arg));
    }

    T t;
};

注意在没有任何可变参数模板的情况下使用完美转发。如果t.test只需要移动类型作为参数,则无法在没有forward<Arg>(arg)的情况下调用它。

这里发生的第二件事是&&没有遵循的参数。将&&添加到ArgTypes将是一个错误,并且会使某些情况无法编译。考虑一下这个简单的案例:

std::function<void(int)> f;
int i = 0;
f(i);

那将无法编译。如果您将&&添加到ArgTypes,则每个非引用的参数(例如int)都将成为调用运算符的右值引用(在我们的示例中为int&&) 。由于所有参数类型都已在std::function参数列表中正确限定,因此您希望在调用运算符中接收的内容正是那些未转换的类型。

如果您不使用std::forward,您需要&&的原因?因为即使您不需要推断值类别,仍然需要不将每个参数复制到包含的函数中。如果其中一个std::function参数为int&,则您不想移动它。但如果其中一个参数是std::unique_ptr<int>,则必须移动它!这正是std::forward的用途。只移动应该移动的东西。

答案 2 :(得分:0)

std::forward只是将rvalue引用附加到类型中,因此将参考折叠规则考虑在内,它会有效地传递引用参数并移动对象参数。