Увеличьте время жизни/область действия объекта из ветки `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 в области видимости за пределами { ... }?


person Phil-ZXX    schedule 18.07.2019    source источник
comment
Возможный дубликат создания объектов в условных операторах C++   -  person Tas    schedule 19.07.2019
comment
Обратите внимание, что std::is_default_constructible_v не проверяет, будет ли компилироваться MyClass myObj;. Оно ошибочно названо. Он в основном проверяет инициализацию значения, а не инициализацию по умолчанию.   -  person Brian Bi    schedule 19.07.2019


Ответы (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>();
}

решает вашу проблему.

Обратите внимание, что в разделе c++17, в приведенном выше коде не происходит копий или перемещений MyClass.

person Yakk - Adam Nevraumont    schedule 19.07.2019
comment
У меня было ощущение, что лямбда-выражения потребуются. Спасибо - person Phil-ZXX; 19.07.2019

Вы не можете продлить время жизни объекта с автоматическим сроком хранения за пределы области, в которой он создан.

Что вы можете сделать, так это создать неинициализированное хранилище за пределами вашего блока 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 не будет отброшена, и вы получите сообщение об ошибке, жалующееся на отсутствие конструктора по умолчанию.

person Miles Budnek    schedule 18.07.2019