So, there I'm trying to comprehend that new & complex concept of the coroutines. Took Clang for it, compiling via clang++ -std=c++17 -fcoroutines-ts -stdlib=libc++ goes fine.
One of the most useful concepts is task<> coroutine type, it's mentioned here and even has a couple interesting implementations, by Gor Nishanov and in cppcoro library.
Okay, looked fine to try myself in the simpliest case. So, the goal is to implement something that should work like the following:
{
auto producer = []() -> task<int> {
co_return 1;
};
auto t = producer();
assert(!t.await_ready());
assert(t.result() == 1);
assert(t.await_ready());
}
The template class task<> itself was made quite straightforward:
#pragma once
#include <experimental/coroutine>
#include <optional>
namespace stdx = std::experimental;
template <typename T=void>
struct task
{
template<typename U>
struct task_promise;
using promise_type = task_promise<T>;
using handle_type = stdx::coroutine_handle<promise_type>;
mutable handle_type m_handle;
task(handle_type handle)
: m_handle(handle)
{}
task(task&& other) noexcept
: m_handle(other.m_handle)
{ other.m_handle = nullptr; };
bool await_ready()
{ return m_handle.done(); }
bool await_suspend(stdx::coroutine_handle<> handle)
{
if (!m_handle.done()) {
m_handle.resume();
}
return false;
}
auto await_resume()
{ return result(); }
T result() const
{
if (!m_handle.done())
m_handle.resume();
if (m_handle.promise().m_exception)
std::rethrow_exception(m_handle.promise().m_exception);
return *m_handle.promise().m_value;
}
~task()
{
if (m_handle)
m_handle.destroy();
}
template<typename U>
struct task_promise
{
std::optional<T> m_value {};
std::exception_ptr m_exception = nullptr;
auto initial_suspend()
{ return stdx::suspend_always{}; }
auto final_suspend()
{ return stdx::suspend_always{}; }
auto return_value(T t)
{
m_value = t;
return stdx::suspend_always{};
}
task<T> get_return_object()
{ return {handle_type::from_promise(*this)}; }
void unhandled_exception()
{ m_exception = std::current_exception(); }
void rethrow_if_unhandled_exception()
{
if (m_exception)
std::rethrow_exception(std::move(m_exception));
}
};
};
Couldn't really make a smaller piece of code complete and compilable, sorry. Anyway it worked somehow, but there still remained the case of task<void>, it's usage could be like the following:
{
int result = 0;
auto int_producer = []() -> task<int> {
co_return 1;
};
auto awaiter = [&]() -> task<> { // here problems begin
auto i1 = co_await int_producer();
auto i2 = co_await int_producer();
result = i1 + i2;
};
auto t = awaiter();
assert(!t.await_ready());
t.await_resume();
assert(result == 2);
}
The latter didn't seem a problem at all, it looked like task_promise<U> required a specialization for void (could be a non-template struct without that void case). So, I tried it:
template<>
struct task_promise<void>
{
std::exception_ptr m_exception;
void return_void() noexcept {}
task<void> get_return_object() noexcept
{ return {handle_type::from_promise(*this)}; }
void unhandled_exception()
{ m_exception = std::current_exception(); }
auto initial_suspend()
{ return stdx::suspend_always{}; }
auto final_suspend()
{ return stdx::suspend_always{}; }
};
Neat and simple... and it causes segfault without any readable stacktrace =(
Works fine when task<> is changed to any non-void template, like task<char>.
What is wrong with my template specialization? Or am I missing some tricky concept with those coroutines?
Would be grateful for any ideas.
Apparently, the usual suspect was the criminal: specialization! From the standard itself [temp.expl.spec]/7
When writing a specialization, be careful about its location; or to make it compile will be such a trial as to kindle its self-immolation.
To avoid issue, let's make it as simple as possible: task_promise can be a non template and the member specialization is declared as soon as possible:
template<class T=void>
struct task{
//...
struct task_promise{
//...
};
};
//member specialization declared before instantiation of task<void>;
template<>
struct task<void>::task_promise{
//...
};
Related
I'm having trouble with creating a certain type of range.
In LLVM (a compiler infrastructure), a Module (could be the code for a translation unit) is organized as follows:
llvm::Module m{...}; // A module could be a single translation unit.
for(const auto &func : m){
for(const auto &basic_block : func){
for(const auto &inst : basic_block){
}
}
}
In the code I'm working on, it's very convenient to have a function that creates a range of instructions contained in a Module or Function. For example:
llvm::Module m;
llvm::Function f;
InstSet temp;
temp.insert(instructions(m));
temp.insert(instructions(f));
However, I'm having trouble elegantly implementing the range.
Things I tried:
Implementing a custom iterator. It turned out to be really messy even though what I'm trying to do is supposed to be simple...
Implementing using coroutines. While this worked out slightly better, the performance was unacceptable (about 10x slower upon investigation).
My question is, are there any elegant solutions that I'm missing? If there is none, I'd like to know why to understand the problem I'm trying to solve the wrong way.
EDIT (I added my own implementations below):
Here is the implementation that uses coroutines (finished):
struct InstructionsGenerator {
struct promise_type;
using Handle = coroutine_handle<promise_type>;
struct promise_type {
const Instruction *i;
auto get_return_object() -> InstructionsGenerator { return {Handle::from_promise(*this)}; }
auto yield_value(const Instruction &y) -> suspend_always {
i = &y;
return {};
}
auto initial_suspend() -> suspend_never { return {}; };
auto final_suspend() -> suspend_always { return {}; };
auto return_void() -> void {}
};
Handle h;
};
auto instructions_generator(const Module &m) -> InstructionsGenerator {
for (const auto &f : m) {
for (const auto &bb : f) {
for (const auto &i : instructions(bb)) {
co_yield i;
}
}
}
}
struct InstructionRange {
struct iterator {
optional<InstructionsGenerator> g;
auto operator++() -> auto & {
g->h();
return *this;
}
auto operator*() const -> auto & { return *(g->h.promise().i); }
auto operator==(const iterator & /*unused*/) const -> bool { return g->h.done(); }
};
iterator b;
[[nodiscard]] auto begin() const -> iterator { return b; }
[[nodiscard]] auto end() const -> iterator { return {nullopt}; };
~InstructionRange() {
if (b.g) {
b.g->h.destroy();
}
}
InstructionRange(InstructionsGenerator fi) : b{fi} {}
InstructionRange(InstructionRange &&r) noexcept : b{r.b} { r.b.g = nullopt; }
};
auto instructions(const Module &m) -> InstructionRange { return {instructions_generator(m)}; };
Here is the implementation that uses custom iterators (unfinished. Abandoned it after realizing it's too much of a hassle.):
// The real implementation was templated but to give you the idea, I've simplified it.
struct LiftedRange {
llvm::Module& m;
public:
struct iterator {
llvm::Module& m;
llvm::Module::iterator l1;
llvm::Function::iterator l2;
auto operator*(); // Skipped for brevity.
auto operator==(); // Skipped for brevity.
auto operator++(){
if(next(l2) != l1.end()){
++l2;
return *this;
}
do {
++l1;
while(l1->empty() || l1 != m.end()); // Avoid empty sets.
l2 = l1->begin();
return *this;
}
};
auto begin(); // Skipped for brevity.
auto end(); // Skipped for brevity.
};
I got a stack overflow exception in visual studio inside a coroutine in a loop and found that the loop had a bug that prevented it from termination but I wondered why the stack was overflowed ? the coroutine might not even was using the stack but the heap instead and even if the stack was used there was no any recursive calls at all
after some experiments I could reproduce the crash with :
msvc 19.28
g++-10 on wsl and mingw64
clang-cl 10 on windows and clang++-10 on linux
this code causes the stack overflow:
#include <stdexcept>
#include <utility>
#include <cstdio>
#ifdef __clang__
#ifdef _WIN32
#pragma message "using clang coroutine header"
#include "clang-cl-coro.h"
#else
#pragma message "using coroutine experimental header"
#include <experimental/coroutine>
#endif
namespace std
{
template<class P = void>
using coroutine_handle = experimental::coroutine_handle<P>;
using suspend_never = experimental::suspend_never;
using suspend_always = experimental::suspend_always;
}
#else
#pragma message "using coroutine header"
#include <coroutine>
#endif
class vtask
{
inline static size_t task_count = 0;
public:
struct promise_type
{
inline static size_t promise_count = 0;
std::coroutine_handle<> waiter;
std::exception_ptr ex_ptr = nullptr;
struct resume_waiter
{
inline static size_t awaiter_count = 0;
std::coroutine_handle<> waiter;
resume_waiter(std::coroutine_handle<> waiter) noexcept : waiter{ waiter }
{
++awaiter_count;
printf("[%zu] resume_waiter(std::coroutine_handle<> waiter)\n", awaiter_count);
}
~resume_waiter()
{
--awaiter_count;
printf("[%zu] ~resume_waiter()\n", awaiter_count);
}
bool await_ready() const noexcept { return false; }
auto await_suspend(std::coroutine_handle<>) noexcept
{
return waiter;
}
void await_resume() const noexcept {}
};
promise_type()
{
++promise_count;
printf("[%zu] vtask::promise_type()\n", promise_count);
}
~promise_type()
{
--promise_count;
printf("[%zu] ~vtask::promise_type()\n", promise_count);
}
vtask get_return_object() { return { *this }; }
constexpr std::suspend_always initial_suspend() noexcept { return {}; }
resume_waiter final_suspend() const noexcept { return { waiter }; }
void unhandled_exception() noexcept
{
ex_ptr = std::current_exception();
}
void return_void() const noexcept {}
};
vtask(promise_type& p) : coro{ std::coroutine_handle<promise_type>::from_promise(p) }
{
++task_count;
printf("[%zu] vtask(promise_type& p)\n", task_count);
}
vtask(vtask&& other) noexcept : coro{ std::exchange(other.coro, nullptr) }
{
++task_count;
printf("[%zu] vtask(vtask&& other)\n", task_count);
}
~vtask()
{
if (coro)
coro.destroy();
--task_count;
printf("[%zu] ~vtask()\n", task_count);
}
bool await_ready() const noexcept
{
return false;
}
void await_suspend(std::coroutine_handle<> waiter)
{
coro.promise().waiter = waiter;
coro.resume();
}
void await_resume() noexcept {}
private:
std::coroutine_handle<promise_type> coro;
};
struct detached_task
{
struct promise_type
{
inline static size_t promise_count = 0;
promise_type()
{
++promise_count;
printf("[%zu] detached_task::promise_type()\n", promise_count);
}
~promise_type()
{
--promise_count;
printf("[%zu] ~detached_task::promise_type()\n", promise_count);
}
detached_task get_return_object() { return {}; }
std::suspend_never initial_suspend() noexcept { return {}; }
std::suspend_never final_suspend() noexcept { return {}; }
void unhandled_exception() noexcept
{
std::terminate();
}
constexpr void return_void() const noexcept {}
};
inline static size_t task_count = 0;
detached_task()
{
++task_count;
printf("[%zu] detached_task()\n", task_count);
}
~detached_task()
{
--task_count;
printf("[%zu] ~detached_task()\n", task_count);
}
};
vtask do_stackoverflow() { co_return; }
detached_task stackoverflow()
{
for (;;)
co_await do_stackoverflow();
}
int main()
{
stackoverflow();
}
command lines used:
cl /std:c++latest coro-stackoverflow.cpp /EHsc for msvc
g++ -std=c++20 coro-stackoverflow.cpp -fcoroutines for mingw64
clang-cl /std:c++latest coro-stackoverflow.cpp /EHsc
g++-10 -std=c++20 coro-stackoverflow.cpp -fcoroutines -o overflow.bug on wsl
clang++-10 -std=c++20 -stdlib=libc++ coro-stackoverflow.cpp -o overflow-clang.bug on wsl
and this is the clang coro header on windows:
#pragma once
namespace std { namespace experimental { inline namespace coroutines_v1 {
template <typename R, typename...> struct coroutine_traits {
using promise_type = typename R::promise_type;
};
template <typename Promise = void> struct coroutine_handle;
template <> struct coroutine_handle<void> {
static coroutine_handle from_address(void *addr) noexcept {
coroutine_handle me;
me.ptr = addr;
return me;
}
void operator()() { resume(); }
void *address() const { return ptr; }
void resume() const { __builtin_coro_resume(ptr); }
void destroy() const { __builtin_coro_destroy(ptr); }
bool done() const { return __builtin_coro_done(ptr); }
coroutine_handle &operator=(decltype(nullptr)) {
ptr = nullptr;
return *this;
}
coroutine_handle(decltype(nullptr)) : ptr(nullptr) {}
coroutine_handle() : ptr(nullptr) {}
// void reset() { ptr = nullptr; } // add to P0057?
explicit operator bool() const { return ptr; }
protected:
void *ptr;
};
template <typename Promise> struct coroutine_handle : coroutine_handle<> {
using coroutine_handle<>::operator=;
static coroutine_handle from_address(void *addr) noexcept {
coroutine_handle me;
me.ptr = addr;
return me;
}
Promise &promise() const {
return *reinterpret_cast<Promise *>(
__builtin_coro_promise(ptr, alignof(Promise), false));
}
static coroutine_handle from_promise(Promise &promise) {
coroutine_handle p;
p.ptr = __builtin_coro_promise(&promise, alignof(Promise), true);
return p;
}
};
template <typename _PromiseT>
bool operator==(coroutine_handle<_PromiseT> const& _Left,
coroutine_handle<_PromiseT> const& _Right) noexcept
{
return _Left.address() == _Right.address();
}
template <typename _PromiseT>
bool operator!=(coroutine_handle<_PromiseT> const& _Left,
coroutine_handle<_PromiseT> const& _Right) noexcept
{
return !(_Left == _Right);
}
template <typename _PromiseT>
bool operator==(coroutine_handle<_PromiseT> const& _Left,
std::nullptr_t) noexcept
{
return _Left.address() == nullptr;
}
template <typename _PromiseT>
bool operator==(std::nullptr_t, coroutine_handle<_PromiseT> const& _Right) noexcept
{
return _Right.address() == nullptr;
}
template <typename _PromiseT>
bool operator!=(coroutine_handle<_PromiseT> const& _Left,
std::nullptr_t) noexcept
{
return !(_Left == nullptr);
}
template <typename _PromiseT>
bool operator!=(std::nullptr_t, coroutine_handle<_PromiseT> const& _Right) noexcept
{
return _Right.address() != nullptr;
}
struct suspend_always {
bool await_ready() { return false; }
void await_suspend(coroutine_handle<>) {}
void await_resume() {}
};
struct suspend_never {
bool await_ready() { return true; }
void await_suspend(coroutine_handle<>) {}
void await_resume() {}
};
}}}
on windows the crash occurs early for msvc and clang-cl builds but mingw64 and wsl builds takes more time
gcc 10.1 which I used seems to have a knows bug where the task is constructed twice but destroyed once leaking a task each iteration which seems to cause the overflow
but clang and msvc don't have this bug and they also crash !
Edit: tried gcc 10.3 mingw64 and it doesn't have the mentioned gcc bug but it also cause a stack overflow ! even faster the the bugged compiler ! maybe this behavior is expected ?
I can't figure out which is wrong with the code above
The problem was in this part of code :
void await_suspend(std::coroutine_handle<> waiter)
{
coro.promise().waiter = waiter;
coro.resume();
}
this asymmetric transfer caused the stackoverflow and changing the code to :
auto await_suspend(std::coroutine_handle<> waiter)
{
coro.promise().waiter = waiter;
return coro;
}
eliminates the problem on clang and msvc but gcc 10.3 still crashes, I think it doesn't support symmetric transfer yet
My goal is to create a generic Event type that can be used in subscribe/notify architecture, but I am having trouble getting class member functions to work.
Event.hpp:
#ifndef EVENT_HPP
#define EVENT_HPP
#include <functional>
#include <unordered_set>
template <typename ... EventParameterTypes>
struct Event {
typedef void(*EventCallback)(EventParameterTypes ...);
template <typename ClassType>
Event &subscribe(ClassType *instance, void(ClassType::*eventCallback)(EventParameterTypes...)) {
auto bound = [=](EventParameterTypes&& ... params) { return ((instance)->*(eventCallback))(std::forward<EventParameterTypes>(params)...); };
return this->subscribe(bound);
}
Event &subscribe(EventCallback eventCallback) {
return this->addEventCallback(eventCallback);
}
void notify(EventParameterTypes ... types) {
for (const auto &it : this->m_eventCallbacks) {
if (it) (*it)(types...);
}
}
private:
std::unordered_set<EventCallback> m_eventCallbacks;
Event &addEventCallback(EventCallback eventCallback) {
auto foundIterator = std::find(this->m_eventCallbacks.begin(), this->m_eventCallbacks.end(), eventCallback);
if (foundIterator != this->m_eventCallbacks.end()) {
return *this;
}
this->m_eventCallbacks.insert(eventCallback);
return *this;
}
};
#endif //EVENT_HPP
Main.cpp:
#include "Event.hpp"
struct EventTest {
using MyEvent = Event<int>;
MyEvent myEvent;
};
void myEventCallback(int) {
//Stuff
}
struct EventListener {
void eventListenerCallback(int) {
//Stuff
}
};
int main() {
EventListener eventListener{};
EventTest eventTest{};
eventTest.myEvent.subscribe(&myEventCallback); //OK
eventTest.myEvent.subscribe(&eventListener, &EventListener::eventListenerCallback); //Compile error
}
Is there any way to resolve this? I have looked into std::bind but it only works with a certain amount of placeholders, and the lambda causes the function to be of a local lambda type.
You are hoping to somehow pack instance and eventCallback into a single function pointer. You can't do that, no more than you can pour an ocean into a thimble. One way out is to make EventCallback a typedef for a suitable specialication of std::function, as in
typedef std::function<void(EventParameterTypes ...)> EventCallback;
I won't be surprised if the rest of the code would just work after this, with no modifications.
Using #Igor Tandetnik s advice, I came up with the following (working) implementation:
Event.hpp
#ifndef EVENT_HPP
#define EVENT_HPP
#include <functional>
#include <functional>
#include <type_traits>
#include <vector>
template<typename T, typename... U>
size_t getFunctionAddress(std::function<T(U...)> f) {
typedef T(fnType)(U...);
fnType **fnPointer = f.template target<fnType *>();
if (fnPointer == nullptr) {
return 0;
}
return (size_t) *fnPointer;
}
template<typename ... EventParameterTypes>
struct Event {
template<class ClassType> using MemberPtr = void (ClassType::*)(EventParameterTypes...);
using EventCallback = std::function<void(EventParameterTypes ...)>;
template<class ClassType>
Event &subscribe(ClassType *instance, MemberPtr<ClassType> eventCallback) {
const auto bound = [=](EventParameterTypes &&... params) { return ((instance)->*eventCallback)(std::forward<EventParameterTypes>(params)...); };
return this->operator+=(bound);
}
Event &operator+=(EventCallback eventCallback) {
return this->addEventCallback(eventCallback);
}
template<class ClassType>
Event &unsubscribe(ClassType *instance, MemberPtr<ClassType> eventCallback) {
const auto bound = [=](EventParameterTypes &&... params) { return ((instance)->*eventCallback)(std::forward<EventParameterTypes>(params)...); };
return this->operator-=(bound);
}
Event &operator-=(EventCallback eventCallback) {
return this->removeEventCallback(eventCallback);
}
template<class ClassType>
bool isListenerRegistered(ClassType *instance, MemberPtr<ClassType> eventCallback) {
const auto bound = [=](EventParameterTypes &&... params) { return ((instance)->*eventCallback)(std::forward<EventParameterTypes>(params)...); };
return this->isListenerRegistered(bound);
}
bool isListenerRegistered(EventCallback eventCallback) {
return findListener(eventCallback) != this->m_eventCallbacks.cend();
}
void operator()(EventParameterTypes ... types) {
this->notify(std::forward<EventParameterTypes>(types)...);
}
void notify(EventParameterTypes ... types) {
for (const auto &it : this->m_eventCallbacks) {
if (it) (it)(std::forward<EventParameterTypes>(types)...);
}
}
private:
std::vector<EventCallback> m_eventCallbacks;
std::mutex m_eventListenerMutex;
typename std::vector<EventCallback>::const_iterator findListener(EventCallback eventCallback) {
for (auto iter = this->m_eventCallbacks.cbegin(); iter != this->m_eventCallbacks.cend(); iter++) {
if (getFunctionAddress(*iter) == getFunctionAddress(eventCallback)) {
return iter;
}
}
return this->m_eventCallbacks.cend();
}
Event &addEventCallback(EventCallback eventCallback) {
auto foundPosition = this->findListener(eventCallback);
if (foundPosition != this->m_eventCallbacks.cend()) {
return *this;
}
this->m_eventCallbacks.emplace_back(eventCallback);
return *this;
}
Event &removeEventCallback(EventCallback eventCallback) {
auto foundPosition = this->findListener(eventCallback);
if (foundPosition == this->m_eventCallbacks.cend()) {
return *this;
}
this->m_eventCallbacks.erase(foundPosition);
return *this;
}
};
#endif //EVENT_HPP
I'm working on an embedded system, so code size is an issue. Using the standard library ups my binary size by about 60k, from 40k to 100k. I'd like to use std::function, but I can't justify it for 60k. Is there a standalone implementation that I can use, or something similar? I'm using it to implicitly cast lambdas in member functions with bound variables in c++ 11.
Here is simple implementation of std::function-like class template without inclusion of any headers. You can customize the behavior as you wish(like move/forward, empty call response, etc):
live_demo
// Scroll down for example of usage
namespace bicycle
{
template<typename Result,typename ...Args>
struct abstract_function
{
virtual Result operator()(Args... args)=0;
virtual abstract_function *clone() const =0;
virtual ~abstract_function() = default;
};
template<typename Func,typename Result,typename ...Args>
class concrete_function: public abstract_function<Result,Args...>
{
Func f;
public:
concrete_function(const Func &x)
: f(x)
{}
Result operator()(Args... args) override
{
return f(args...);
}
concrete_function *clone() const override
{
return new concrete_function{f};
}
};
template<typename Func>
struct func_filter
{
typedef Func type;
};
template<typename Result,typename ...Args>
struct func_filter<Result(Args...)>
{
typedef Result (*type)(Args...);
};
template<typename signature>
class function;
template<typename Result,typename ...Args>
class function<Result(Args...)>
{
abstract_function<Result,Args...> *f;
public:
function()
: f(nullptr)
{}
template<typename Func> function(const Func &x)
: f(new concrete_function<typename func_filter<Func>::type,Result,Args...>(x))
{}
function(const function &rhs)
: f(rhs.f ? rhs.f->clone() : nullptr)
{}
function &operator=(const function &rhs)
{
if( (&rhs != this ) && (rhs.f) )
{
auto *temp = rhs.f->clone();
delete f;
f = temp;
}
return *this;
}
template<typename Func> function &operator=(const Func &x)
{
auto *temp = new concrete_function<typename func_filter<Func>::type,Result,Args...>(x);
delete f;
f = temp;
return *this;
}
Result operator()(Args... args)
{
if(f)
return (*f)(args...);
else
return Result{};
}
~function()
{
delete f;
}
};
}
// ___________________[ Example of usage ]___________________ //
int func1(double)
{
return 1;
}
struct Functor2
{
int operator()(double)
{
return 2;
}
};
double func3(bool,int)
{
return 3.0;
}
struct Functor4
{
double operator()(bool,int)
{
return 4.0;
}
};
int main()
{
int res = 10;
{
bicycle::function<int(double)> f{func1};
res -= f(1.0);
f = Functor2{};
res -= f(2.0);
}
{
bicycle::function<double(bool,int)> f1;
f1 = func3;
bicycle::function<double(bool,int)> f2{f1};
res -= f2(true,1);
f1 = Functor4{};
f2 = f1;
res -= f2(false,2);
}
return res;
}
The 60k came from exception handling being added by the compiler, because exceptions were required for std::function. std::function only throws one exception, "bad_function_call". So I removed the code that threw the exception, now it seg faults if an empty function is called, and I saved myself 60k.
I am trying to mimic a finally like effect. So i thought i should run a quick dirty test.
The idea was to use Most Important const to stop destruction and to put the finally block in a lambda. However apparently i did something wrong and its being called at the end of MyFinally(). How do i solve this problem?
#include <cassert>
template<typename T>
class D{
T fn;
public:
D(T v):fn(v){}
~D(){fn();}
};
template<typename T>
const D<T>& MyFinally(T t) { return D<T>(t); }
int d;
class A{
int a;
public:
void start(){
int a=1;
auto v = MyFinally([&]{a=2;});
try{
assert(a==1);
//do stuff
}
catch(int){
//do stuff
}
}
};
int main() {
A a;
a.start();
}
My Solution code (Note: You can not have two finally in the same block. as expect. But still kind of dirty)
#include <cassert>
template<typename T>
class D{
T fn; bool exec;
public:
D(T v):fn(v),exec(true){}
//D(D const&)=delete //VS doesnt support this yet and i didnt feel like writing virtual=0
D(D &&d):fn(move(d.fn)), exec(d.exec) {
d.exec = false;
}
~D(){if(exec) fn();}
};
template<typename T>
D<T> MyFinally(T t) { return D<T>(t); }
#define FINALLY(v) auto OnlyOneFinallyPlz = MyFinally(v)
int d;
class A{
public:
int a;
void start(){
a=1;
//auto v = MyFinally([&]{a=2;});
FINALLY([&]{a=2;});
try{
assert(a==1);
//do stuff
}
catch(int){
FINALLY([&]{a=3;}); //ok, inside another scope
try{
assert(a==1);
//do other stuff
}
catch(int){
//do other stuff
}
}
}
};
void main() {
A a;
a.start();
assert(a.a==2);
}
Funny enough, if you remove the & in MyFinally in the original code it works -_-.
// WRONG! returning a reference to a temporary that will be
// destroyed at the end of the function!
template<typename T>
const D<T>& MyFinally(T t) { return D<T>(t); }
You can fix it my introducing a move constructor
template<typename T>
class D{
T fn;
bool exec;
public:
D(T v):fn(move(v)),exec(true){}
D(D &&d):fn(move(d.fn)), exec(d.exec) {
d.exec = false;
}
~D(){if(exec) fn();}
};
And then you can rewrite your toy
template<typename T>
D<T> MyFinally(T t) { return D<T>(move(t)); }
Hope it helps. No "const reference" trick is needed when you work with auto. See here for how to do it in C++03 with const references.
Your code and Sutter's are not equivalent. His function returns a value, yours returns a reference to an object that will be destroyed when the function exits. The const reference in the calling code does not maintain the lifetime of that object.
The problem stems from the use of a function maker, as demonstrated by Johannes.
I would argue that you could avoid the issue by using another C++0x facility, namely std::function.
class Defer
{
public:
typedef std::function<void()> Executor;
Defer(): _executor(DoNothing) {}
Defer(Executor e): _executor(e) {}
~Defer() { _executor(); }
Defer(Defer&& rhs): _executor(rhs._executor) {
rhs._executor = DoNothing;
}
Defer& operator=(Defer rhs) {
std::swap(_executor, rhs._executor);
return *this;
}
Defer(Defer const&) = delete;
private:
static void DoNothing() {}
Executor _executor;
};
Then, you can use it as simply:
void A::start() {
a = 1;
Defer const defer([&]() { a = 2; });
try { assert(a == 1); /**/ } catch(...) { /**/ }
}
Well the problem is explained by others, so I will suggest a fix, exactly in the same way Herb Sutter has written his code (your code is not same as his, by the way):
First, don't return by const reference:
template<typename T>
D<T> MyFinally(T t)
{
D<T> local(t); //create a local variable
return local;
}
Then write this at call site:
const auto & v = MyFinally([&]{a=2;}); //store by const reference
This became exactly like Herb Sutter's code.
Demo : http://www.ideone.com/uSkhP
Now the destructor is called just before exiting the start() function.
A different implementation which doesn't use auto keyword anymore:
struct base { virtual ~base(){} };
template<typename TLambda>
struct exec : base
{
TLambda lambda;
exec(TLambda l) : lambda(l){}
~exec() { lambda(); }
};
class lambda{
base *pbase;
public:
template<typename TLambda>
lambda(TLambda l): pbase(new exec<TLambda>(l)){}
~lambda() { delete pbase; }
};
And use it as:
lambda finally = [&]{a=2; std::cout << "finally executed" << std::endl; };
Looks interesting?
Complete demo : http://www.ideone.com/DYqrh
You could return a shared_ptr:
template<typename T>
std::shared_ptr<D<T>> MyFinally(T t) {
return std::shared_ptr<D<T>>(new D<T>(t));
}