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&)>;:drawingis not modified when not explicitly typing asauto&.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?
>Solution :
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.
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.