When I have a C++ lambda that captures an object by value, and that object can throw an exception in its copy constructor, clang-tidy will show a warning:
warning: an exception may be thrown in function '(lambda at :15:23)' which should not throw exceptions [bugprone-exception-escape]
I don't understand why this warning appears.
Example code:
#include <functional>
class MyClass {
public:
MyClass() {}
MyClass(const MyClass& rhs) { throw std::exception(); }
void do_stuff() const {}
};
void inner_func(std::function<void()>&& the_func) {}
void outer_func() {
MyClass test_obj;
auto the_lambda = [=]() { test_obj.do_stuff(); };
inner_func(std::move(the_lambda));
}
(on godbolt.org: https://godbolt.org/z/67xW6Yenx)
The clang-tidy warning is shown for the line where the_lambda
is created.
I don't understand why the lambda (or creating the lambda) must not throw exceptions. In the example, outer_func()
does not care whether an exception happens. inner_func()
also should not care about this.
Also, apparently the lambda needs to be std::move()d into another function for the warning to appear.
So it seems that merely creating the lambda is a problem already. But why is that the case? And how can I catch this exception that is warned about, or how else can I avoid the warning?
On Godbolt I tried this with clang-tidy 16 and 19, with same results.
The warning is triggered because the closure type of the_lambda
has a throwing move constructor. And, as others pointed out, one of the purposes of this check is to report throwing move constructors.
Why is the move constructor of the_lambda
throwing? Because it internally calls the copy constructor of MyClass
, which can throw. Note that the move constructor of a closure type is defaulted [expr.prim.lambda.closure/14/2], which makes it defined here as throwing.
A simple demo of the same problem without lambdas:
struct MyClass {
MyClass() {}
MyClass(const MyClass& rhs) { throw 1; }
};
struct Lambda {
MyClass myclass;
Lambda(MyClass param) : myclass(param) { }
Lambda(const Lambda&) = default;
Lambda(Lambda&&) = default; // this calls throwing copy constructor of `MyClass`
};
void outer_func() {
MyClass obj;
Lambda l1(obj);
Lambda l2(std::move(l1)); // this calls throwing move constructor of 'Lambda'
}
Godbolt link: https://godbolt.org/z/hze55P9Gb