从 `if constexpr` 分支扩展对象 lifetime/scope
Extend object lifetime/scope from a `if constexpr` branch
假设我们有以下代码
struct MyClass
{
MyClass() = delete; // or MyClass() { }
MyClass(int) { }
void func() { }
};
int main()
{
if constexpr (std::is_default_constructible_v<MyClass>) {
MyClass myObj;
} else {
MyClass myObj(10);
}
myObj.func(); // Error
}
这里我使用 if constexpr
来确定 class 是否是默认构造的(或不是),然后相应地创建一个对象。在某种程度上,我 天真地 认为这会将不同的分支简化为正确的分支,即
if constexpr (true) {
/* instruction branch 1 */
} else if constexpr (false) {
/* instruction branch 2 */
}
就变成了
/* instruction branch 1 */
但现实中,大概更像这样
{
/* instruction branch 1 */
}
但是问题就变成了(回到第一个例子),我怎样才能将 myObj
保持在 { ... }
之外的范围内?
您不能将具有自动存储持续时间的对象的生命周期延长到超出其创建范围的范围。
您可以做的是在 if
块之外创建未初始化的存储,并在 if
范围内的存储中创建一个对象。最简单的方法可能是 std::optional
:
template <typename T>
void foo() {
std::optional<T> obj;
if constexpr (std::is_default_constructible_v<T>) {
obj.emplace();
} else {
obj.emplace(10);
}
obj->func();
}
虽然这确实会导致少量开销,因为 std::optional
必须持有一个额外的标志来确定它是否持有一个对象。如果您想避免这种开销,您可以自己管理存储:
template <typename T>
void foo() {
std::aligned_storage_t<sizeof(T), alignof(T)> storage;
T* ptr;
if constexpr (std::is_default_constructible_v<T>) {
ptr = new(&storage) T{};
} else {
ptr = new(&storage) T{10};
}
struct destroy {
destroy(T* ptr) : ptr_{ptr} {}
~destroy() { ptr_->~T(); }
T* ptr_;
} destroy{ptr};
ptr->func();
}
请注意,在这两种情况下,我都将功能移到了函数模板中。 if constexpr
要丢弃分支,它必须依赖于模板参数。如果您尝试直接在 main
中执行此操作,则 false 分支将不会被丢弃,您将收到一个错误,抱怨缺少默认构造函数。
首先,您的代码无法运行。 if constexpr
确实需要它的条件依赖。
我会解决的。
template<class MyClass>
void func() {
MyClass myObj = []{
if constexpr (std::is_default_constructible_v<MyClass>) {
return MyClass{};
} else {
return MyClass(10);
}
}();
myObj.func();
}
现在
int main() {
func<MyClass>();
}
解决了您的问题。
请注意,根据 c++17 规则,上述代码中不会出现 MyClass
的复制或移动。
假设我们有以下代码
struct MyClass
{
MyClass() = delete; // or MyClass() { }
MyClass(int) { }
void func() { }
};
int main()
{
if constexpr (std::is_default_constructible_v<MyClass>) {
MyClass myObj;
} else {
MyClass myObj(10);
}
myObj.func(); // Error
}
这里我使用 if constexpr
来确定 class 是否是默认构造的(或不是),然后相应地创建一个对象。在某种程度上,我 天真地 认为这会将不同的分支简化为正确的分支,即
if constexpr (true) {
/* instruction branch 1 */
} else if constexpr (false) {
/* instruction branch 2 */
}
就变成了
/* instruction branch 1 */
但现实中,大概更像这样
{
/* instruction branch 1 */
}
但是问题就变成了(回到第一个例子),我怎样才能将 myObj
保持在 { ... }
之外的范围内?
您不能将具有自动存储持续时间的对象的生命周期延长到超出其创建范围的范围。
您可以做的是在 if
块之外创建未初始化的存储,并在 if
范围内的存储中创建一个对象。最简单的方法可能是 std::optional
:
template <typename T>
void foo() {
std::optional<T> obj;
if constexpr (std::is_default_constructible_v<T>) {
obj.emplace();
} else {
obj.emplace(10);
}
obj->func();
}
虽然这确实会导致少量开销,因为 std::optional
必须持有一个额外的标志来确定它是否持有一个对象。如果您想避免这种开销,您可以自己管理存储:
template <typename T>
void foo() {
std::aligned_storage_t<sizeof(T), alignof(T)> storage;
T* ptr;
if constexpr (std::is_default_constructible_v<T>) {
ptr = new(&storage) T{};
} else {
ptr = new(&storage) T{10};
}
struct destroy {
destroy(T* ptr) : ptr_{ptr} {}
~destroy() { ptr_->~T(); }
T* ptr_;
} destroy{ptr};
ptr->func();
}
请注意,在这两种情况下,我都将功能移到了函数模板中。 if constexpr
要丢弃分支,它必须依赖于模板参数。如果您尝试直接在 main
中执行此操作,则 false 分支将不会被丢弃,您将收到一个错误,抱怨缺少默认构造函数。
首先,您的代码无法运行。 if constexpr
确实需要它的条件依赖。
我会解决的。
template<class MyClass>
void func() {
MyClass myObj = []{
if constexpr (std::is_default_constructible_v<MyClass>) {
return MyClass{};
} else {
return MyClass(10);
}
}();
myObj.func();
}
现在
int main() {
func<MyClass>();
}
解决了您的问题。
请注意,根据 c++17 规则,上述代码中不会出现 MyClass
的复制或移动。