5

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.

7
  • This is somewhat unrelated to your question, but what stops you from doing a template <typename T> using Lazy = std::optional<T>. I take this approach all the time to define a lazy initialized var. Commented Jul 28, 2019 at 5:53
  • I didn't want to write lazy-construction if(!a) a.emplace(...); everytime I use a's function. I wanted some fixed initialization (often with lengthy arguments) to be done when a is 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.) Commented Jul 28, 2019 at 5:58
  • 1
    I really think it is a gcc bug, as using initializer list for m_val also fixes issue Demo. Commented Jul 28, 2019 at 21:53
  • I am not convinced this is a bug. I do not see how a compiler can be required to constant initialize variables with external storage as constant initialization needs to happen at compile time while the actual initialization of an external var may reside in a translation unit not visible at compile time. Commented Jul 28, 2019 at 22:59
  • @cplusplusrat: Lazy<A> a{[] { return A{}; }}; could be const initialized (at compile time), then int val = a->f(); is dynamically initialized. Commented Jul 29, 2019 at 7:47

1 Answer 1

1

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

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.