Search code examples
c++gccinitializationconstexprstdoptional

std::optional default constructor not being constexpr in gcc?


I have the following code to test my constexpr-constructible lazy class:

https://godbolt.org/z/rMLCiL

#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.


Solution

  • Is this a bug in gcc, or am I missing something?

    Yes, one can verify that this bug (segmentation fault during program execution) was still reproducible till GCC 9.4, and the program runs nicely starting from GCC 10.1 (meaning that the bug was fixed). Demo: https://gcc.godbolt.org/z/osWa1no9Y