1

I am trying have generic "catch certain signal" function, that would handle the signal in specific manner.

The functions are in different flavours; some with no return (void) some with returns (bool) and some with parameters (actually only on).

I got it to compile with three variants of the same function:

int64_t run_inside_abort_check_v( std::function<void(Device*)> fun_ptr );
template<typename Arg>
int64_t run_inside_abort_check_p( std::function<void(Device*, Arg )> fun_ptr, Arg arg );
int64_t run_inside_abort_check_r( bool* ret_value, std::function<bool (Device*)> fun_ptr );

But this would require slightly different three implementations -- that seems stupid.

How could i combine these three into single function?

As an example, the sample version for the single arg version:

template<typename Arg>
int64_t Class::run_inside_abort_check( std::function<void(Device*, Arg)> fun_ptr, Arg args )
{
    try
    {
      if ( flag_abort )
         throw SignalAborted();

      fun_ptr( this->device,  arg ); // Call the wanted function that might thrown SignalToCatch
    }
    catch ( const SignalToCatch& sig )
    {
       device->handle_trouble_case();
       return (int64_t)ERROR_TROUBLE;
    }
    return 0x00;
}

As @VTT pointed out the cases (1) and (2) are similar, other is with empty args: When i try such the compiling fails:

    #include <iostream>
    #include <functional>

    class Device
    {
    public:
        void foo1() { std::cout << "foo called\n"; };
        void foo2( int bar ) { std::cout << "foo2 called:" << bar << "\n"; };
    };

    template<typename Arg>
    int64_t run_inside_abort_check_p( std::function<void(Device*, Arg )> fun_ptr, Arg arg );

    template<typename ... Arg>
    int64_t run_inside_abort_check_va( std::function<void(Device*, Arg...  )> fun_ptr, Arg ... arg );

    int main()
    {
        int foo;
        run_inside_abort_check_p<int>( &Device::foo2, foo ); // works fine!
        run_inside_abort_check_va<int>( &Device::foo2, foo );
    }

Produces:

 error: no matching function for call to ‘run_inside_abort_check_va<int>(void (Device::*)(int), int&)’

 silly.cpp:18:9: note:   template argument deduction/substitution failed:
 silly.cpp:23:56: note:   mismatched types ‘std::function<void(Device*, Arg ...)>’ and ‘void (Device::*)(int)’
 run_inside_abort_check_va<int>( &Device::foo2, foo );
susundberg
  • 650
  • 7
  • 14
  • number 1 and number 2 are the same, `Arg` can be just an empty parameter pack. Third case is completely different – user7860670 Oct 19 '18 at 12:28
  • Thanks for your reply, i made edit trying to point on problem with that. The (3) case is falls to same category nicely by changing the return value to parameter as pointer/ref. – susundberg Oct 19 '18 at 18:29

2 Answers2

1

I personally don't like the return of value via possible null pointer parameter. So I return via a class that encapsulates the error code and the optional function return value. A great complication comes from the fact that void is an incomplete type and you can't store the return of a function returning void. So I specialize this class for returning void type:

template <class T> struct Check_result
{
    std::int64_t error_code;
    std::optional<T> return_value;
};

template <> struct Check_result<void>
{
    std::int64_t error_code;
};

I try to avoid using std::function when the function doesn't need to be stored and so for the check function I use a template parameter. Remember the complication with void I was talking about? Here's where C++17 comes in handy with if constexpr.

template<class F, class R = decltype(std::declval<F>()(device))>
auto run_inside_abort_check(F f) -> Check_result<R>
{
    try
    {
        if constexpr (std::is_void_v<R>)
        {
            f(device);
            return {std::int64_t{0}};
        }                
        else
        {
            return {std::int64_t{0}, f(device)};
        }
    }
    catch (const SignalToCatch& sig)
    {
        handle_trouble_case();

        if constexpr (std::is_void_v<R>)
            return {std::int64_t{11}};
        else
            return {std::int64_t{11}, {}};
    }
}

An alternative to simplify the logic and control flow inside maybe_void(f, device) is to create a complete type equivalent of void and a helper function maybe_void that converts void to our complete type:

struct Complete_void{};

template <class F, class... Args> auto maybe_void(F&& f, Args&&... args)
{
    using R = decltype(std::forward<F>(f)(std::forward<Args>(args)...));

    if constexpr (std::is_void_v<R>)
    {
        std::forward<F>(f)(std::forward<Args>(args)...);
        return Complete_void{};        
    }
    else
    {
        return std::forward<F>(f)(std::forward<Args>(args)...);
    }
}

We next modify Check_result to handle Complete_void:

template <> struct Check_result<void>
{
    std::int64_t error_code;
    std::optional<Complete_void> return_value;
};

And now we can simplify our function greatly:

template<class F, class R = decltype(std::declval<F>()(device))>
auto run_inside_abort_check(F f) -> Check_result<R>
{
    try
    {
        return {std::int64_t{0}, maybe_void(f, device)};
    }
    catch (const SignalToCatch& sig)
    {
        handle_trouble_case();
        return {std::int64_t{11}, {}};
    }
}

Usage:

class Device
{
public:
    void foo1() {};
    void foo2(int) {};
    int foo3(int, int);
};

int main()
{
    X x;
    x.device = Device{};

    Check_result r1 = x.run_inside_abort_check([](Device& d) { return d.foo1();});
    r1.error_code;

    Check_result r2 = x.run_inside_abort_check([](Device& d) { return d.foo2(24);}) ;
    r2.error_code;

    Check_result r3 = x.run_inside_abort_check([](Device& d) { return d.foo3(24, 13);});
    r3.error_code;

    if (r3.return_value)
    {
        int r = *r3.return_value;
    }
}

C++14 solution

If you don't need to handle both returning void and non-void cases then the above can trivially be adapted to C++14.

If you need to handle both cases, that can also be done in C++14:

For std::optional use boost::optional or, if that isn't available, use std::unique_ptr.

As for the if constexpr, well here we use SFINAE and we duplicate for the two cases. I don't see a way around it:

template<class F, class R = decltype(std::declval<F>()(device))>
auto run_inside_abort_check(F f) -> std::enable_if_t<std::is_void_v<R>, Check_result<R>>
{
    try
    {
        f(device);
        return Check_result<R>{std::int64_t{0}};
    }
    catch (const SignalToCatch& sig)
    {
        handle_trouble_case();

        return Check_result<R>{std::int64_t{11}};
    }
}

template<class F, class R = decltype(std::declval<F>()(device))>
auto run_inside_abort_check(F f) -> std::enable_if_t<!std::is_void_v<R>, Check_result<R>>
{
    try
    {
        return Check_result<R>{std::int64_t{0}, std::make_unique<R>(f(device))};
    }
    catch (const SignalToCatch& sig)
    {
        handle_trouble_case();

        return Check_result<R>{std::int64_t{11}, nullptr};

    }
}
bolov
  • 72,283
  • 15
  • 145
  • 224
0

The answear, that i see suite compes from solving the compiling issue as described here:Template substitution failure with std::function

The function object needs to be explicitely stated like this:

  run_inside_abort_check_va( std::function<void(Device*,int)>{ &Device::foo2}, foo );
  run_inside_abort_check_va( std::function<void(Device*)>{ &Device::foo1} );

Then everything works fine (as long as for case (3) one moves the return value as parameter).

susundberg
  • 650
  • 7
  • 14