Here is a code that crash when, in main
, line (2) version is used (and line (1) is commented). Weird enough, this code compiles fines with a simple replacement implementation (line (1)) that mimic the behavior of line (2). Of course, if it's an undefined behavior, it can't have a good explanation, but I don't understand why it crashes. Basically, it's a generator implementation coroutine in C++, tested with captures by references. It works always, except when used with unique_ptr
(raw pointer works). Just, why ?
#include <iostream>
#include <coroutine>
#include <cassert>
#include <optional>
#include <memory>
using std::cout;
using std::endl;
template<typename T>
class generator
{
public:
struct promise_type
{
std::optional<T> t_;
promise_type() = default;
~promise_type() = default;
std::suspend_always initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void unhandled_exception() {}
generator get_return_object() { return {std::coroutine_handle<promise_type>::from_promise(*this)}; }
std::suspend_always yield_value(T t) { t_ = t; return {}; }
void return_void() {}
};
private:
std::coroutine_handle<promise_type> h_;
generator(std::coroutine_handle<promise_type> h) : h_(h) {}
public:
generator() = default;
// ------ Prevent copies
generator(const generator&) = delete;
generator& operator=(const generator&) = delete;
// ------ Allow moves
generator(generator&& other) noexcept
: h_(move(other.h_)) // move may be unnecessary, coroutine_handle acts like a lightweight pointer
{
other.h_ = {}; // Unlink handle in moved generator
// move() does not guarantee to destroy original value
}
generator& operator=(generator&& other) noexcept
{
h_ = move(other.h_);
other.h_ = {};
return *this;
}
~generator()
{
if(h_)
{
h_.destroy();
h_ = {};
}
}
bool is_resumable() const
{
return h_ && !h_.done();
}
bool operator()()
{
return resume();
}
bool resume()
{
assert(is_resumable());
h_();
return !h_.done();
}
[[nodiscard]] const T& get() const
{
return h_.promise().t_.value();
}
[[nodiscard]] T& get() // Allow movable
{
return h_.promise().t_.value();
}
};
struct F
{
/*F(const std::function<generator<int>()>& del)
{
handle = del();
}*/
template<typename T>
F(T del)
{
handle = del();
}
~F() { cout << "dtor" << endl; }
generator<int> handle;
};
template<typename T>
struct UniquePtr
{
UniquePtr(T* t) : t_(t) {}
UniquePtr(UniquePtr&&) = delete;
UniquePtr(const UniquePtr&) = delete;
UniquePtr& operator=(UniquePtr&&) = delete;
UniquePtr& operator=(const UniquePtr&) = delete;
~UniquePtr() { delete t_; }
T* operator->() const { return t_;}
private:
T* t_;
};
int main()
{
int x = 10;
auto a = [&]() -> generator<int> {
x = 20;
co_yield x;
};
//UniquePtr<F> ptr(new F(a)); // (1)
std::unique_ptr<F> ptr(new F(a)); // (2)
generator<int>& gen = ptr->handle;
gen();
cout << gen.get() << "/" << x << endl;
return 0;
}
EDIT. :
It crashes also a Godbolt (error 139), here is the link : https://godbolt.org/z/cWYY8PKx4. Maybe is it a gcc implementation problem, around std::unique_ptr
optimizations? I can't test on other compilers on Godbolt, there is no support for coroutines on clang.