I have the following code to test my constexpr-constructible lazy class:
#include <optional>
template <class T>
class Lazy
{
using initializer_t = T (*)();
std::optional<T> m_val = std::nullopt;
initializer_t m_initializer;
public:
constexpr Lazy(initializer_t initializer = initializer_t{[] { return T{}; }}) noexcept
: m_initializer{initializer} {}
T& operator*()
{
if (!m_val.has_value()) {
m_val = m_initializer();
}
return *m_val;
}
constexpr T* operator->() { return &(**this); }
};
#include <iostream>
struct A {
int f() { return 10; }
~A()
{
std::cout << "Goodbye A " << (void*)this << std::endl;
}
};
extern Lazy<A> a;
int val = a->f();
Lazy<A> a{[] { return A{}; }};
int main()
{
std::cout << val << std::endl;
}
I expect it to print 10 in main. When compiled in clang-8.0, it runs as expected, but when compiled in gcc (either in 8.3 or in trunk), it causes a segmentation fault. It seems that a is not constant-initialized, and it's calling null a.m_initializer inside int val = a->f() before a gets initialized.
Cppreference says that std::optional<T> can be initialized to std::nullopt using a constexpr constructor, whether T is trivially-destructible or not. Thus, Lazy<A> a{[] { return A{}; }} should be constant-initialized before int val = a->f(); is initialized. If I comment out A::~A, it will run as expected even when compiled with gcc. Is this a bug in gcc, or am I missing something?
Update: I also found that if I make std::optional<T> a base class instead of having such member, it works correctly in gcc. Also, if I just change the line std::optional<T> m_val = std::nullopt; to std::optional<T> m_val;, it works correctly (std::optional<T> m_val{}; doesn't work). I don't really understand.
template <typename T> using Lazy = std::optional<T>. I take this approach all the time to define a lazy initialized var.if(!a) a.emplace(...);everytime I usea's function. I wanted some fixed initialization (often with lengthy arguments) to be done whenais used the first time. I also often want to do some post-initialization on unmovable object (which I removed from the code above for simplicity.)m_valalso fixes issue Demo.Lazy<A> a{[] { return A{}; }};could be const initialized (at compile time), thenint val = a->f();is dynamically initialized.