Search for a trick to avoid creating a field in the class template

I have a protection class such as a class (this is a 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 so that a close expression can also be specified 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 additional field const std::function<void()>& close; to pass lambda from constructor to destructor.

Is there a way to avoid this extra field while preserving the lambda (and the good syntax when using)?

+5
source share
5 answers

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

 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, use 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 use:

 #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"; } 

Conclusion:

 Open 0 Body Close 1 

I checked that it works for gcc and clang, C ++ 14 with no errors / warnings.

+4
source

This is generally impossible. Unlike a function pointer, a lambda can capture and, therefore, contain runtime. True, your lambda does not do this and therefore can be converted to a function pointer, but this does not make it a template argument.

+3
source

If you can take a little cheating: type lambda in the base class instead of 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"; } ); } 
+2
source

Is there a way to avoid this extra field while preserving the lambda (and the good syntax when using)?

Yes: if you notice, then nothing will work out by transferring an open function to your guard of the field of visibility (therefore, the principle of shared responsibility means that you should not have it there).

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

You must make the type independent of the type of template. It will also make the syntax more natural in the client code.

You must make sure that 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_; }; 

Then your client code will look like this:

 int x() { operation.begin_transaction(); Guard commit{ [&operation](){ operation.commit_transaction(); } }; // do things & stuff here } 
+1
source

Perhaps you can use the answer to this question . I hope I'm not mistaken, but if you could use a pointer to the constructor, you could pass it to type_traits (look at the first answer in this question) and get the second argument, which will be a close function, and then you could be called.

Since it is not possible to get a pointer to a constructor, perhaps you can use another member function to initialize your object?

0
source

All Articles