11

This code compiles and runs, throwing the int:

#include <functional>

void r( std::function<void() noexcept> f ) { f(); }

void foo() { throw 1; }

int main()
{
    r(foo);
}

However I would like the compiler to reject the line r(foo); because r should only be passed a noexcept function. The noexcept specifier appears to be ignored. Is there any way to achieve that?

Edit: This question is different to Is knowledge about noexcept-ness supposed to be forwarded when passing around a function pointer? because I am asking for a remedy, specifically in the case of std::function.

Community
  • 1
  • 1
M.M
  • 138,810
  • 21
  • 208
  • 365
  • Possible dupe? https://stackoverflow.com/questions/14003502/is-knowledge-about-noexcept-ness-supposed-to-be-forwarded-when-passing-around-a?rq=1 – Jonathan Potter Jul 24 '15 at 05:57
  • @JonathanPotter certainly related, although I don't think it answers my question (Is there any way to cause `r(foo)` to be rejected) – M.M Jul 24 '15 at 06:00
  • 2
    If `noexcept` isn't part of the function's type then I'd guess not. – Jonathan Potter Jul 24 '15 at 06:01
  • I also have this problem. I have an function that will complete asynchronously, and the handler provider wants to indicate that it's exception won't propagate. Is this an over site? – Werner Erasmus Jul 28 '15 at 09:02
  • Looks like even with C++11 noexcept, exception specification is not still a reliable part of C++ :-( – Serge Ballesta Jul 28 '15 at 13:43
  • The code in the question has stopped compiling in clang 4.0 and gcc 7.0 ( seem to have compiled fine in earlier releases ). – Zitrax Mar 27 '17 at 12:18

1 Answers1

1

I've also stumbled across this problem. My solution was to use a delegating object (delegating to the std::function). The delegate has a no-except specification. It could still be improved (move added, etc.).

Here it goes:

#include <functional>

template <class FuncT>
struct NoExceptDelegate;

template <class R, class ... Args >
struct NoExceptDelegate<R(Args...)>
{
    NoExceptDelegate(std::function<R(Args...)>&& callback)
      : callback_(move(callback))
    {
        if (!callback_)
        {
            throw std::invalid_argument( "NoExceptDelegate requires a valid callback");
        }
    }

    template <class...ArgsU>
    R operator()(ArgsU&&... args) noexcept
    {
        return callback_(std::forward<ArgsU>(args)...);
    }

  private:
      std::function<R(Args...)> callback_;
};

This is typically used as a contract in an asynchronous interface to indicate that the provided handler shall not throw e.g.:

struct Interface
{
    virtual void doSomethingAsynchronous(
        NoExceptDelegate<void(int)> onCompletionResult) = 0;
    //...etc
};

As the client is the callback provider, NoExceptDelegate is a promise from the provider that provided shall not fail. The provider should ensure that at least std::function provided is callable.

Werner Erasmus
  • 3,988
  • 17
  • 31
  • That's not really correct though, as std::function may throw bad_function_call for empty std::functions. – Puppy Jul 28 '15 at 11:32
  • In that case one could catch it during construction, which is not during invocation. – Werner Erasmus Jul 28 '15 at 11:34
  • Yep seems reasonable enough to me to prevent nullability as an invariant. – Puppy Jul 28 '15 at 15:13
  • What compiler does this work on? It did not work for me using g++ -std=c++11 with g++ version 5.4.0. – Kurt M Feb 14 '17 at 18:04
  • @KurtM, at that stage I tested it on GCC 4.8 and the compiler shipped with Visual Studio, as well as the latest clang at the time that it was written – Werner Erasmus Feb 15 '17 at 11:28
  • 1
    Is it correct to assume that there is no way to make this a compile time guarantee ? I.e. a promise is the best we can do ? – Zitrax Mar 27 '17 at 12:24
  • @Zitrax, I think http://stackoverflow.com/questions/39763401/can-a-noexcept-function-still-call-a-function-that-throws-in-c17 answers your question, which boils down to - yes, the promise is presently the best. BTW, kudos for fixing the code... – Werner Erasmus Mar 27 '17 at 15:23
  • The `operator()` method should also be marked `const`. I also think, IMO, that there should be a default constructor which fills `callback_` with an empty lambda, such as `[](){}`, just for ease-of-transition from `std::function`. – Chris Watts Jan 02 '18 at 22:48