Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

handling void type in template

I have a templated function that invokes another function and stores its return value, then does some work before returning the value. I'd like to extend this to handle T = void, and was wondering if specialization is my only option.

template<typename T>
T Foo( T(*Func)() ) 
{
    // do something first (e.g. some setup)
    T result = Func();
    // do something after (e.g. some tear down)
    return result;
}

// Is this specialization the only option?
template<>
void Foo<void>( void(*Func)() ) 
{
    // do something first (e.g. some setup)
    Func();
    // do something after (e.g. some tear down)
    return;
}

void Bar() {}
int BarInt() { return 1; }

int main()
{
    Foo<int>(&BarInt);
    Foo<void>(&Bar);
}

Or can the regular version of Foo be modified to handle the void type and basically do nothing in that case? I was thinking that maybe my local result could be wrapped in a type that could handle void maybe, but could also see the assignment as being a deal-breaker.

like image 562
JaredC Avatar asked Jan 07 '13 23:01

JaredC


2 Answers

Given that your operation does not depend on the result of the function, you can do it without a specialization. It is ok for a function returning void to return an expression of type void. So the return part is not the troublesome one, but you need to figure out a way to do the pre and post operations. Constructors and destructors will help you there:

struct do_something_helper
{
    do_something_helper()
    {
        // do something first (e.g. take a lock)
    }
    ~do_something_helper()
    {
        // do something after (e.g. release a lock)
    }
};

Then you can write your function like this:

template<typename T>
T Foo( T(*Func)() ) 
{
    do_something_helper _dummy_helper; // constructor called here

    return Func();
    // destructor called here
}

For a more general solution using lambdas as you commented, it could look like this:

template< typename Pre, typename Post >
struct scope_guard
{
    scope_guard( Pre&& pre, Post&& post )
      : _post( std::forward< Post >( post ) )
    {
        pre();
    }
    ~scope_guard()
    {
        _post();
    }

    Post _post;
};

template< typename Pre, typename Post >
scope_guard< Pre, Post > make_scope_guard( Pre&& pre, Post&& post )
{
    return scope_guard< Pre, Post >( std::forward< Pre >( pre ), std::forward< Post >( post ) );
}

template<typename T>
T Foo( T(*Func)() ) 
{
    auto do_something_helper =
        make_scope_guard(
            [](){ /* do something first (e.g. take a lock) */ },
            [](){ /* do something after (e.g. release a lock) */ }
        );

    return Func();
}

A type-erased version using std::function< void() > would be easier to write and use, but it would be rather inefficient.

like image 195
K-ballo Avatar answered Sep 23 '22 06:09

K-ballo


If all you're doing is taking and releasing a lock then you should use RAII instead of calling lock/unlock functions. This is especially true if Func could throw, as if it does the code after it will not be called.

Once you have an RAII lock (or some time of RAII object if you need more than locks), you can simply do this which works for void:

template<typename T>
T Foo( T(*Func)() ) 
{
    lock my_lock;
    return Func();
}

template<typename T>
T Foo( T(*Func)() ) 
{
    struct raii_wrapper {
      raii_wrapper(T(*Func)()) : Func(Func) {
        // pre effects
      }
      ~raii_wrapper() {
        // post effects
      }
      T(*Func)();
    } actions(Func);
    return Func;
}
like image 36
Pubby Avatar answered Sep 22 '22 06:09

Pubby