0

假设我们有以下代码

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范围保持在{ ... }?

4

2 回答 2

3

您不能将具有自动存储持续时间的对象的生命周期延长到其创建范围之外。

您可以做的是在您的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丢弃分支,它必须依赖于模板参数。如果您尝试直接在mainfalse 分支中执行此操作,则不会被丢弃,并且您将收到一个错误,抱怨缺少默认构造函数。

于 2019-07-18T23:29:17.043 回答
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规则下,上述代码中不会出现复制或移动。

于 2019-07-19T00:56:44.317 回答