5

I have a scope guard like class (this is the simplified test case):

template<void(*close)()>
struct Guard1
{
  template<typename O>
  Guard1(O open) { open(); }

  ~Guard1() { close(); }
};

void close() { std::cout << "close g1\n"; }

int main()
{
  Guard1<close> g1 = [](){ std::cout << "open g1\n"; };
}

I modified it such that the close expression can also be given as a lambda:

class Guard2
{
  std::function<void()> close;
public:

  template<typename O, typename C>
  Guard2(O open, C close) : close(close)
  {
    open();
  }

  ~Guard2() { close(); }
};


int main()
{
  Guard2 g2(
      [](){ std::cout << "open g2\n"; },
      [](){ std::cout << "close g2\n"; });
}

However I had to introduce an extra field const std::function<void()>& close; to pass the lambda from the constructor to the destructor.

Is there a way to avoid this extra field while still keeping the lambda (and a nice syntax when used as well)?

Micha Wiedenmann
  • 19,979
  • 21
  • 92
  • 137
  • 1
    possible duplicate of [How can I store a lambda expression as a field of a class in C++11?](http://stackoverflow.com/questions/9186510/how-can-i-store-a-lambda-expression-as-a-field-of-a-class-in-c11) – Ami Tavory Jun 18 '15 at 14:01
  • I would bet on no. In Guard1 you hold 'close' in the template parameters, if you want a lambda then this is no longer possible – marom Jun 18 '15 at 14:05
  • 1
    @AmiTavory, the OP wants to do the opposite -- find a way to obviate the need to store the lambda or anything else as a member variable. – R Sahu Jun 18 '15 at 14:10
  • I doubt this is possible, see http://www.cplusplus.com/forum/general/112742/#msg616042 – m.s. Jun 18 '15 at 14:14

5 Answers5

4

Since you want to use it only as ScopeGuard - then you can be sure that const reference or rvalue reference to your close() are valid. You need a member or base class as in other answer - but this is not very big difference. But you can have it as rvalue reference to your lambda, not to std::function which is of quite big performance cost:

template <class Close>
class ScopeGuard {
public:
    template <typename Open>
    ScopeGuard(Open&& open, Close&& close) 
         : close(std::forward<Close>(close))
    {
        open();
    }
    ScopeGuard(ScopeGuard&& other) : close(std::move(other.close))
    {}
    ~ScopeGuard()
    {
        close();
    }
private:
    Close&& close;
};

To make it easier to use - have this make function:

template <class Open, class Close>
auto makeScopeGuard(Open&& open, Close&& close)
{
    return ScopeGuard<Close>(std::forward<Open>(open), 
                             std::forward<Close>(close));
}

And usage:

#include <iostream>
using namespace std;

int main() 
{
   int i = 0;
   auto scope = makeScopeGuard([&i]{cout << "Open " << i++ << "\n";}, 
                               [&i]{cout << "Close " << i++ << "\n";});
   cout << "Body\n";
}

Output:

Open 0
Body
Close 1

I verified it works for gcc and clang, C++14 without errors/warnings.

PiotrNycz
  • 23,099
  • 7
  • 66
  • 112
3

It's generally not possible. Unlike a function pointer, a lambda can capture and therefore contain run-time state. True, your lambda does not, and therefore can be converted to a function pointer, but that doesn't make it a template argument.

MSalters
  • 173,980
  • 10
  • 155
  • 350
2

If you can accept a bit of cheating: stuff the lambda into a base class instead of a field:

#include <iostream>

template<typename Base>
struct Guard1 : public Base
{
  template<typename O>
  Guard1(O open, Base base ) : Base(base) { open(); }
  Guard1(Guard1 const& rhs) : Base(static_cast<Base const&>(rhs)) { }

  ~Guard1() { (*this)(); }
};

template<typename O, typename C>
Guard1<C> makeGuard(O o, C c) { return Guard1<C>(o,c); }

int main()
{

  auto g1 = makeGuard([](){ std::cout << "open g1\n"; },
                      [](){ std::cout << "close g1\n"; } );
}
MSalters
  • 173,980
  • 10
  • 155
  • 350
1

Is there a way to avoid this extra field while still keeping the lambda (and a nice syntax when used as well)?

Yes: If you observe, there is nothing to be gained by passing the open function to your scope guard (therefore the Single Responsibility Principle states you should not have it there).

You should also pass the function as a runtime parameter, not a template parameter. This will allow for more natural syntax in client code.

You should make the type independent on the template type. This will also make for more natural syntax in client code.

You should ensure the destructor does not throw.

class Guard final
{
public:
    Guard1(std::function<void()> at_scope_exit)
    : f_(std::move(at_scope_exit))
    {
    }

    ~Guard1() noexcept { try{ f_(); } catch(...) {} }
private:
    std::function<void()> f_;
};

Your client code should then look like this:

int x()
{
    operation.begin_transaction();
    Guard commit{ [&operation](){ operation.commit_transaction(); } };

    // do things & stuff here
}
Micha Wiedenmann
  • 19,979
  • 21
  • 92
  • 137
utnapistim
  • 26,809
  • 3
  • 46
  • 82
  • :) "states" in this case is a verb; [SRP](https://en.wikipedia.org/wiki/Single_responsibility_principle) is the "Single Responsibility Principle", wich says (states) that an object should have a single responsiblity. That is, if the object's main responsibility is to call a functor on destruction, it should probably not have other responsibilities as well. – utnapistim Jun 19 '15 at 08:29
  • I think your answer is miss leading. "Yes: ...", but the field is still there, so no. Passing it as template parameter is fine and avoids the creation of a std::function for lambdas. The syntax for the client is fine with a `makeGuard` function. I am also not sure whether swallowing an exception is better than terminating, but this question is not the place for this discussion. However I still learned something from your answer, in particular I have missjudged the `open` argument. Thank you. – Micha Wiedenmann Jun 19 '15 at 18:12
0

Maybe you can use the answer of this question. Hope I am not wrong, but if it was possible to use the constructor's pointer, you could pass it to type_traits (look at the first answer in that question), and get the second argument which would be close function, and then you could alias it.

Since it is not possible to get the constructor's pointer, maybe you can use an other member function to initialize your object ?

Community
  • 1
  • 1
Othman Benchekroun
  • 1,998
  • 2
  • 17
  • 36
  • I either do not understand your proposal or this will result in another field, but I was looking for a way to avoid the field. So I do not think, that is a valid answer. Although it is quite interesting. – Micha Wiedenmann Jun 18 '15 at 15:06