如何限制 lambda 参数的参数只能作为引用?

kat*_*kak 6 c++ c++-concepts c++20

我正在尝试设计一个像这样的简单函数:

void draw(Callback callback)
{
    Drawing drawing("name");
    callback(drawing);

    std::cout << drawing.name() << std::endl;
}
Run Code Online (Sandbox Code Playgroud)

wheredrawing应该作为引用传递给回调,以便调用者可以像这样修改它:

draw([](auto& drawing) {
    drawing.set_name("another name");
});
Run Code Online (Sandbox Code Playgroud)

现在这可以工作了,因为我将回调参数显式键入为auto&(或Drawing&)。但是,当我仅将参数键入为auto( 或Drawing) 时,代码也会编译,但不再按预期工作。该drawing实例是复制的,而不是通过引用传递的。

当 lambda 参数未显式键入为引用时,我希望代码不再编译。

我尝试过以下方法:

  • using Callback = std::function<void(Drawing&)>;:drawing当未明确键入 as 时不会被修改auto&
  • template<typename T> concept Callback = std::is_invocable<void, T, Drawing&>;: 和上面一样。
  • using Callback = void (*)(Drawing&);:这确实有效,但我失去了使用捕获的能力。

那么我该如何正确输入 Callback 以便只有Drawing&lambda 才是有效的参数呢?

Jan*_*tke 9

您可以进行限制,draw以便不能使用右值接受来调用它callback

template <typename Callback>
  requires (std::invocable<Callback, Drawing&> && !std::invocable<Callback, Drawing&&>)
void draw(Callback callback)
{
    Drawing drawing("name");
    callback(drawing);

    std::cout << drawing.name() << std::endl;
}
Run Code Online (Sandbox Code Playgroud)

如果给定的callback接受autoauto&&,它将不满足约束:

int main() {
    draw([](auto& drawing) {}); // OK
    draw([](auto drawing) {});  // error: no matching function call ...
}
Run Code Online (Sandbox Code Playgroud)

请参阅编译器资源管理器中的实时示例

为什么这有效

唯一不能绑定到右值的引用是非常量左值引用。通过限制函数以使回调不能接受右值,我们取消了采用DrawingDrawing&&const Drawing&和 的回调const Drawing&&,因为所有这些都可以绑定到右值。

本质上,我们强制用户采取Drawing&(或auto&),并且意外复制是不可能的,因为左值引用无法绑定到临时对象。

注意:在进行任何分析之前,右值引用都会转换为相同类型的 x 值;这就是为什么我说“右值”而不是谈论这些项目符号中的引用。

进一步建议

请注意,如果您的目标是Drawing不意外复制,则可以通过显式删除其复制构造函数和复制赋值运算符来更可靠地防止这种情况。然而,这显然适用于任何地方,而不仅仅是在draw.