【问题标题】:Extend object lifetime/scope from a `if constexpr` branch从“if constexpr”分支扩展对象生命周期/范围
【发布时间】:2019-11-27 21:55:07
【问题描述】:

假设我们有以下代码

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 来确定该类是否可默认构造(或不可构造),然后相应地创建一个对象。在某种程度上,我天真地认为这会将不同的分支简化为正确的分支,即

    if constexpr (true) {
        /* instruction branch 1 */
    } else if constexpr (false) {
        /* instruction branch 2 */
    }

变成了

    /* instruction branch 1 */

但实际上,可能更像这样

    {
        /* instruction branch 1 */
    }

但是问题变成了(回到第一个示例),我怎样才能将myObj 保持在{ ... } 之外的范围内?

【问题讨论】:

  • 请注意std::is_default_constructible_v 不会测试MyClass myObj; 是否会编译。它被误导性地命名。它基本上测试值初始化,而不是默认初始化。

标签: c++ scope c++17 constexpr if-constexpr


【解决方案1】:

您不能将具有自动存储期限的对象的生命周期延长到超出其创建范围。

您可以做的是在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();
}

Live Demo

这确实会导致少量开销,因为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();
}

Live Demo


请注意,在这两种情况下,我都将功能移至函数模板。要让if constexpr 丢弃分支,它必须依赖于模板参数。如果您尝试直接在 main 中执行此操作,则不会丢弃错误分支,并且您将收到一个错误,抱怨缺少默认构造函数。

【讨论】:

    【解决方案2】:

    首先,您的代码不起作用。 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>();
    }
    

    解决您的问题。

    请注意,在 规则下,上述代码中不会出现MyClass 的复制或移动。

    【讨论】:

    • 我感觉需要 lambda。谢谢
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-09-24
    • 1970-01-01
    • 2020-11-15
    • 1970-01-01
    相关资源
    最近更新 更多