Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why do I get "exception spec is more lax than base" error with this piece of code?

Trying to compile the following code using the clang version in Xcode 6.1 (clang-600.0.54 based on LLVM 3.5svn), with -std=c++11 and -stdlib=libc++ gives me some errors that I don't really understand.

#include <functional>

struct Impl
{
    typedef std::function<void ()> L;
    L l;
    int i;
};

struct Hndl
{
    Impl* impl;
    Hndl(Impl* i): impl(i) {}
    ~Hndl() noexcept(false) {}
};

int main(int argc, char * argv[]) {
    Hndl h(new Impl());
    h.impl->l = [=]
    {
        h.impl->i = 42;
    };
    return 0;
}

Results:

In file included from t.cpp:1:
/Applications/Xcode-6.1.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/functional:1293:52: error: exception specification of overriding
      function is more lax than base version
template<class _FD, class _Alloc, class _FB> class __func;
                                                   ^
/Applications/Xcode-6.1.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/functional:1593:13: note: in instantiation of template class
      'std::__1::__function::__func<<lambda at t.cpp:20:14>, std::__1::allocator<<lambda at t.cpp:20:14> >, void ()>' requested here
        if (sizeof(_FF) <= sizeof(__buf_) && is_nothrow_copy_constructible<_Fp>::value)
            ^
/Applications/Xcode-6.1.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/functional:1697:5: note: in instantiation of function template
      specialization 'std::__1::function<void ()>::function<<lambda at t.cpp:20:14> >' requested here
    function(_VSTD::forward<_Fp>(__f)).swap(*this);
    ^
t.cpp:20:12: note: in instantiation of function template specialization 'std::__1::function<void ()>::operator=<<lambda at t.cpp:20:14> >' requested here
        h.impl->l = [=]
                  ^
/Applications/Xcode-6.1.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/functional:1281:39: note: overridden virtual function is here
    _LIBCPP_INLINE_VISIBILITY virtual ~__base() {}
                                      ^
1 error generated.

Looks as if Impl::L::~L() somehow inherits the noexcept(false) of Hndl::~Hndl(), but I don't know why. Interestingly the same code compiles if I comment out the assignment to h.impl->i inside the lambda. Also compiles if I remove the noexcept(false) specification from Hndl::~Hndl(), but I need that (would be a bit long to explain why, but I do). Also compiles if the lambda captures by ref, but the whole point here would be to be able to copy handles that share an implementation. Adding noexcept(true) to Impl::~Impl() doesn't help.

ideone.com's c++11 compiler happily compiles it as it is.

Could anyone explain me what's happening here?

like image 783
imre Avatar asked Sep 29 '22 12:09

imre


2 Answers

The lambda captures h, which has a potentially-throwing destructor, so the lambda's closure type also has a potentially-throwing destructor.

With that in mind, you can reduce the problem to this:

#include <functional>

struct F
{
    void operator()() { }

    ~F() noexcept(false) {}
};

int main() {
    std::function<void ()> f = F{};
}

It seems that the std::function in libc++ cannot store a callable type without a nothrow destructor which looks like a bug in libc++.

From the error messages it looks as though the fix might be as simple as adding an explicit noexcept to the __func destructor, but I'm not familiar with the implementation so it might not be that easy.

I don't see any obvious workarounds except wrapping your Hndl type in another type with a noexcept destructor, so capturing it in the lambda does not make the lambda have a noexcept(false) destructor. I tried something like this, but libc++ seems to have similar problems in shared_ptr:

    std::function<void ()> f = std::bind(&Hndl::operator(), std::make_shared<Hndl>());
like image 177
Jonathan Wakely Avatar answered Oct 16 '22 07:10

Jonathan Wakely


I think the problem is that Hndl is captured in lambda by value and std::function destructor is treated as noexcept(true) since it is not stated otherwise in it's definition. So Hndl instance cannot be safely destroyed in l destructor. As for why error disappears if member assignment is removed from lambda - most likely captured value is just optimized away by compiler.

like image 1
dewaffled Avatar answered Oct 16 '22 06:10

dewaffled