我习惯将lambda函数(和其他callables)传递给模板函数 - 并使用它们 - 如下所示
template <typename F>
auto foo (F && f)
{
// ...
auto x = std::forward<F>(f)(/* some arguments */);
// ...
}
我的意思是:我习惯通过转发引用传递它们并称它们通过std::forward
。
另一个Stack Overflow用户争论(请参阅this answer的评论),这个函数调用函数两次或更多次,这是危险的,因为当使用r值引用调用函数时,它在语义上无效且可能存在危险(也可能是未定义行为)。
我部分地误解了他的意思(我的错),但剩下的疑问是,如果下面的bar()
函数(在同一个对象上有一个不容置疑的多个std::forward
)它是正确的代码或它(可能只是潜在的)危险。
template <typename F>
auto bar (F && f)
{
using A = typename decltype(std::function{std::forward<F>(f)})::result_type;
std::vector<A> vect;
for ( auto i { 0u }; i < 10u ; ++i )
vect.push_back(std::forward<F>(f)());
return vect;
}
我会说一般规则适用于这种情况。在移动/转发变量后,您不应该对变量执行任何操作,除非可以分配给变量。
从而...
如何正确使用通过转发引用传递的callable?
只有在您确定不会再次呼叫时(即在最后一次呼叫时,如果有的话),只能前进。
如果从未调用过多次,则没有理由不转发。
至于为什么你的代码片段可能有危险,请考虑以下仿函数:
template <typename T>
struct foo
{
T value;
const T &operator()() const & {return value;}
T &&operator()() && {return std::move(value);}
};
作为优化,operator()
在rvalue上调用时允许调用者从value
移动。
现在,如果给出这个仿函数,你的模板将无法编译(因为,正如T.C.所说,std::function
在这种情况下无法确定返回类型)。
但如果我们改变了一点:
template <typename A, typename F>
auto bar (F && f)
{
std::vector<A> vect;
for ( auto i { 0u }; i < 10u ; ++i )
vect.push_back(std::forward<F>(f)());
return vect;
}
然后它给break spectacularly给这个仿函数。
前进只是一个有条件的举动。
因此,一般来说,多次转发相同的东西与多次移动一样危险。
未评估的前锋不会移动任何东西,所以这些都不算数。
通过std::function
进行路由增加了一个皱纹:该推导仅适用于函数指针和函数对象,其中单个函数调用运算符不是&&
限定的。对于这些,如果两者都编译,则rvalue和左值调用总是等效的。
如果你要将调用者转发到另一个地方或者只是简单地调用callable一次,我会争辩说使用std::forward
是正确的事情。正如here所解释的那样,这将保留可调用的值类别,并允许调用可能重载的函数调用操作符的“正确”版本。
原始线程中的问题是在循环中调用了callable,因此可能会多次调用。另一个帖子的具体例子是
template <typename F>
auto map(F&& f) const
{
using output_element_type = decltype(f(std::declval<T>()));
auto sequence = std::make_unique<Sequence<output_element_type>>();
for (const T& element : *this)
sequence->push(f(element));
return sequence;
}
在这里,我相信调用std::forward<F>(f)(element)
而不是f(element)
,即
template <typename F>
auto map(F&& f) const
{
using output_element_type = decltype(std::forward<F>(f)(std::declval<T>()));
auto sequence = std::make_unique<Sequence<output_element_type>>();
for (const T& element : *this)
sequence->push(std::forward<F>(f)(element));
return sequence;
}
可能会有问题。就我的理解而言,右值的定义特征是它不能明确地被引用。特别是,自然无法在表达式中多次使用相同的prvalue(至少我不能想到一个)。此外,据我所知,如果你使用std::move
或std::forward
或其他任何方式来获得xvalue,即使在同一个原始对象上,每次结果都将是一个新的xvalue。因此,也不可能有一种方法不止一次地引用相同的xvalue。由于相同的rvalue不能多次使用,我认为(参见this answer下面的注释)对于重载的函数调用操作符来说,做一些只能在调用发生时才能执行的操作通常是有效的。一个右值,例如:
class MyFancyCallable
{
public:
void operator () & { /* do some stuff */ }
void operator () && { /* do some stuff in a special way that can only be done once */ }
};
MyFancyCallable
的实现可能假设选择&&
限定版本的调用不可能发生多次(在给定对象上)。因此,我会考虑将同一个callable转发到多个语句中进行语义分解。
当然,从技术上讲,对于转发或移动物体实际意味着什么并没有通用的定义。最后,实际上由所涉及的特定类型的实现来赋予其意义。因此,您可以简单地指定作为接口的一部分,传递给您的算法的潜在callables必须能够处理在引用同一对象的rvalue上多次调用。但是,这样做几乎违背了关于如何在C ++中使用右值引用机制的所有约定,我真的没有看到可能会从这样做中获得什么......