从`if constexpr`分支扩展对象的生存期/范围

时间:2019-07-18 22:31:35

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

说我们有以下代码

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保留在{ ... }之外的范围内?

2 个答案:

答案 0 :(得分: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();
}

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中执行此操作,则不会丢弃false分支,并且会因抱怨缺少默认构造函数而出错。

答案 1 :(得分: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的副本或移动。