可调用的完美转发
Perfect forwarding of a callable
我想出了以下代码来将 R()
-like 转换为 void()
-like 可调用对象:
#include <utility>
template<class Callable>
auto discardable(Callable&& callable)
{ return [&]() { (void) std::forward<Callable>(callable)(); }; }
// ^-- is it ok?
int main()
{
auto f = discardable([n=42]() mutable { return n--; });
f();
}
我担心被引用捕获。
- 定义明确吗?
- 我能保证
callable
在其生命周期结束后永远不会被复制和使用吗?
这是标记为 C++14,但适用于以下所有标准。
您的程序是 UB,因为您使用捕获的 lambda 的悬空引用。
那么对于,你可以使用
template<class Callable>
auto discardable(Callable&& callable)
{
return [f = std::conditional_t<
std::is_lvalue_reference<Callable>::value,
std::reference_wrapper<std::remove_reference_t<Callable>>,
Callable>{std::forward<Callable>(callable)}]
{
std::forward<Callable>(f)();
};
}
它移动构造临时 lambda。
由于 callable
可以是一个 xvalue,它有可能在 lambda 捕获之前被销毁,因此在捕获中留下一个悬空引用。为防止这种情况,如果参数是右值,则需要复制它。
一个工作示例:
template<class Callable>
auto discardable(Callable&& callable) { // This one makes a copy of the temporary.
return [callable = std::move(callable)]() mutable {
static_cast<void>(static_cast<Callable&&>(callable)());
};
}
template<class Callable>
auto discardable(Callable& callable) {
return [&callable]() mutable {
static_cast<void>(callable());
};
}
如果 callable
是左值引用但它的生命周期范围小于 discardable
返回的 lambda 捕获的范围,您仍然会面临生命周期问题。因此,始终移动或复制 callable
.
可能是最安全和最简单的方法
附带说明一下,尽管有新的专用实用程序可以完美转发函数对象的值类别,例如 std::apply
, the standard library algorithms 总是通过按值接受函数对象来复制它们。因此,如果同时重载 operator()()&
和 operator()()&&
,标准库将始终使用 operator()()&
。
Lambda 是带有 operator()
的匿名结构,捕获列表是一种指定其成员类型的奇特方式。通过引用捕获真的就像它听起来的那样:你有引用成员。不难看出参考悬挂。
在这种情况下,您特别不想完美转发:根据参数是左值还是右值引用,您有不同的语义。
template<class Callable>
auto discardable(Callable& callable)
{
return [&]() mutable { (void) callable(); };
}
template<class Callable>
auto discardable(Callable&& callable)
{
return [callable = std::forward<Callable>(callable)]() mutable { // move, don't copy
(void) std::move(callable)(); // If you want rvalue semantics
};
}
我想出了以下代码来将 R()
-like 转换为 void()
-like 可调用对象:
#include <utility>
template<class Callable>
auto discardable(Callable&& callable)
{ return [&]() { (void) std::forward<Callable>(callable)(); }; }
// ^-- is it ok?
int main()
{
auto f = discardable([n=42]() mutable { return n--; });
f();
}
我担心被引用捕获。
- 定义明确吗?
- 我能保证
callable
在其生命周期结束后永远不会被复制和使用吗?
这是标记为 C++14,但适用于以下所有标准。
您的程序是 UB,因为您使用捕获的 lambda 的悬空引用。
那么对于
template<class Callable>
auto discardable(Callable&& callable)
{
return [f = std::conditional_t<
std::is_lvalue_reference<Callable>::value,
std::reference_wrapper<std::remove_reference_t<Callable>>,
Callable>{std::forward<Callable>(callable)}]
{
std::forward<Callable>(f)();
};
}
它移动构造临时 lambda。
由于 callable
可以是一个 xvalue,它有可能在 lambda 捕获之前被销毁,因此在捕获中留下一个悬空引用。为防止这种情况,如果参数是右值,则需要复制它。
一个工作示例:
template<class Callable>
auto discardable(Callable&& callable) { // This one makes a copy of the temporary.
return [callable = std::move(callable)]() mutable {
static_cast<void>(static_cast<Callable&&>(callable)());
};
}
template<class Callable>
auto discardable(Callable& callable) {
return [&callable]() mutable {
static_cast<void>(callable());
};
}
如果 callable
是左值引用但它的生命周期范围小于 discardable
返回的 lambda 捕获的范围,您仍然会面临生命周期问题。因此,始终移动或复制 callable
.
附带说明一下,尽管有新的专用实用程序可以完美转发函数对象的值类别,例如 std::apply
, the standard library algorithms 总是通过按值接受函数对象来复制它们。因此,如果同时重载 operator()()&
和 operator()()&&
,标准库将始终使用 operator()()&
。
Lambda 是带有 operator()
的匿名结构,捕获列表是一种指定其成员类型的奇特方式。通过引用捕获真的就像它听起来的那样:你有引用成员。不难看出参考悬挂。
在这种情况下,您特别不想完美转发:根据参数是左值还是右值引用,您有不同的语义。
template<class Callable>
auto discardable(Callable& callable)
{
return [&]() mutable { (void) callable(); };
}
template<class Callable>
auto discardable(Callable&& callable)
{
return [callable = std::forward<Callable>(callable)]() mutable { // move, don't copy
(void) std::move(callable)(); // If you want rvalue semantics
};
}