0

Say we have the following code

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
}

Here I am using if constexpr to determine whether the class is default-constructible (or not), and then create an object accordingly. In a way, I naively thought this would simplify the different branches down to just the one that's true, i.e.

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

simply becomes

    /* instruction branch 1 */

But in reality, it is probably more like this

    {
        /* instruction branch 1 */
    }

But then the question becomes (going to back to the the very first example), how can I can I keep myObj in scope outside the { ... }?

Phil-ZXX
  • 2,359
  • 2
  • 28
  • 40
  • Possible duplicate of [Create objects in conditional c++ statements](https://stackoverflow.com/questions/9346477/create-objects-in-conditional-c-statements) – Tas Jul 18 '19 at 22:34
  • Note that `std::is_default_constructible_v` doesn't test whether `MyClass myObj;` would compile. It is misleadingly named. It basically tests value initialization, not default initialization. – Brian Bi Jul 19 '19 at 15:34

2 Answers2

3

You can't extend the lifetime of an object with automatic storage duration beyond the scope in which it's created.

What you can do is create uninitialized storage outside your if block and create an object in that storage within the scope of the if. The easiest way to do that is probably 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

This does result in a small amount of overhead though, since std::optional has to hold an extra flag to determine if it holds an object or not. If you want to avoid that overhead you could manage the storage yourself:

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


Note that in both cases I've moved the functionality to a function template. For if constexpr to discard a branch it must be dependent on a template parameter. If you try to do this directly in main the false branch will not be discarded and you will get an error complaining about a missing default constructor.

Miles Budnek
  • 28,216
  • 2
  • 35
  • 52
2

First, your code won't work. if constexpr really needs its condition to be dependent.

I'll fix it.

template<class MyClass>
void func() {
  MyClass myObj = []{
    if constexpr (std::is_default_constructible_v<MyClass>) {
      return MyClass{};
    } else {
      return MyClass(10);
    }
  }();
  myObj.func();
}

now

int main() {
  func<MyClass>();
}

solves your problem.

Note that under rules, no copies or moves of MyClass occur in the above code.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524