我有以下代码用于测试我的constexpr可构造的lazy类:
#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;
}
我希望它在
main
中输出10。在clang-8.0中编译时,它按预期运行,但在gcc(无论是8.3还是trunk版本)中编译时,它会导致分段错误。似乎a
没有被常量初始化,并且在a
初始化之前,在int val = a->f()
内部调用了空的a.m_initializer
。
Cppreference表示,可以使用constexpr
构造函数将std::optional<T>
初始化为std::nullopt,无论T是否是平凡析构的。因此,在初始化int val = a->f();
之前,Lazy<A> a{[] { return A{}; }}
应该被常量初始化。如果我注释掉A::~A
,即使使用gcc编译,它也将按预期运行。这是gcc的一个bug还是我漏掉了什么?
更新:我还发现,如果我将std::optional<T>
作为基类而不是拥有这样的成员,则在gcc中可以正常工作。另外,如果我只是将std::optional<T> m_val = std::nullopt;
改为std::optional<T> m_val;
,它也可以正常工作(std::optional<T> m_val{};
不起作用)。我真的不理解。
template <typename T> using Lazy = std::optional<T>
。我经常采用这种方法来定义一个惰性初始化的变量。 - cplusplusrata
的函数时都写懒惰构造if(!a) a.emplace(...);
。我希望在第一次使用a
时完成一些固定的初始化(通常具有冗长的参数)。我还经常希望对不可移动对象进行一些后期初始化(为简单起见,我已从上面的代码中删除了它)。 - eivourm_val
的初始化列表也可以解决问题演示。 - Jarod42