Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to restrict lambda parameter's parameter to only be a reference?

I'm trying to design a simple function like this:

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

    std::cout << drawing.name() << std::endl;
}

where drawing should be passed to the callback as a reference, so that the caller can modify it like so:

draw([](auto& drawing) {
    drawing.set_name("another name");
});

Now this works, because I'm explicitly typing the callback parameter as auto& (or Drawing&). However, when I just type the parameter as auto (or Drawing), the code also compiles but no longer works as expected. The drawing instance is copied rather than passed by reference.

I'd like the code to no longer compile when the lambda parameter is not explicitly typed as a reference.

I've tried the following:

  • using Callback = std::function<void(Drawing&)>;: drawing is not modified when not explicitly typing as auto&.
  • template<typename T> concept Callback = std::is_invocable<void, T, Drawing&>;: same as above.
  • using Callback = void (*)(Drawing&);: this actually works, but I loose the ability to use capture.

So how do I go about properly typing Callback so that only Drawing& is a valid parameter to the lambda?

like image 551
katkak Avatar asked Jul 29 '26 08:07

katkak


1 Answers

You could constrain draw so that it cannot be called with an rvalue-accepting 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;
}

If the given callback accepted auto or auto&&, it would not satisfy the constraint:

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

See live example at Compiler Explorer.

Why this works

The only kind of reference that cannot bind to rvalues is a non-const lvalue reference. By constraining the function so that the callback cannot accept an rvalue, we disqualify callbacks that take Drawing, Drawing&&, const Drawing&, and const Drawing&&, since all of these can bind to an rvalue.

Essentially, we force the user to take Drawing& (or auto&), and accidental copying is impossible because lvalue references cannot bind to temporary objects.

Note: rvalue references are converted to xvalues of the same type prior to any analysis; that's why I'm saying "rvalues" and not talking about references in these bullets.

Further advice

Note that if your goal is that Drawing isn't accidentally copied, you can more reliably prevent this by explicitly deleting its copy constructor and copy assignment operator. However, that would obviously apply everywhere, not just within draw.

like image 145
Jan Schultke Avatar answered Jul 31 '26 00:07

Jan Schultke



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!