8

How to in-place construct an optional aggregate? It seems I can only construct an optional single thing, and not an optional aggregate of things.

#include <optional>
#include <iostream>

struct Unmovable
{
    Unmovable(const Unmovable&) = delete;
    Unmovable(Unmovable&&) = delete;
    Unmovable& operator=(const Unmovable&) = delete;
    Unmovable& operator=(Unmovable&&) = delete;

    explicit Unmovable(const char* msg) {
        std::cout << msg << '\n';
    }
};

struct Things
{
    Unmovable one;
    Unmovable two;
};

int main(int argc, char* argv[]) {
    const bool y = argc > 1 && argv[1][0] == 'y';

    std::optional<Unmovable> optionalThing = y
        ? std::optional<Unmovable>{"works"}
        : std::nullopt;
    
    std::optional<Things> optionalThings = y
        ? std::optional<Things>{
#if ATTEMPT == 1
            "jadda", "neida"
#elif ATTEMPT == 2
            {"jadda", "neida"}
#elif ATTEMPT == 3
            Things{"jadda", "neida"}
#elif ATTEMPT == 4
            Unmovable{"jadda"}, Unmovable{"neida"}
#elif ATTEMPT == 5
            {Unmovable{"jadda"}, Unmovable{"neida"}}
#elif ATTEMPT == 6
            Things{Unmovable{"jadda"}, Unmovable{"neida"}}
#elif ATTEMPT == 7
            std::in_place_t{}, "jadda", "neida"
#elif ATTEMPT == 8
            std::in_place_t{}, {"jadda", "neida"}
#elif ATTEMPT == 9
            std::in_place_t{}, Things{"jadda", "neida"}
#elif ATTEMPT == 10
            std::in_place_t{}, Unmovable{"jadda"}, Unmovable{"neida"}
#elif ATTEMPT == 11
            std::in_place_t{}, {Unmovable{"jadda"}, Unmovable{"neida"}}
#elif ATTEMPT == 12
            std::in_place_t{}, Things{Unmovable{"jadda"}, Unmovable{"neida"}}
#endif
        } : std::nullopt;
}
Deduplicator
  • 44,692
  • 7
  • 66
  • 118
user2394284
  • 5,520
  • 4
  • 32
  • 38
  • 2
    The Standard Library types usually use `()` to initialize members, therefore aggregate initialization is not supported. Here specifically: https://eel.is/c++draft/optional#ctor-13 – dyp Apr 28 '21 at 11:49
  • As a result, you can work around this issue either by providing a constructor for Things, or using a wrapper type which forward to `{}`-style init: https://compiler-explorer.com/z/P431GjaEv – dyp Apr 28 '21 at 11:51
  • See the documentation for `emplace`(). – Sam Varshavchik Apr 28 '21 at 12:00
  • 1
    `Things(char const* msg1, char const* msg2) : one{msg1}, two{msg2} {}` and `std::make_optional("jadda", "neida")`. – Eljay Apr 28 '21 at 12:10
  • @SamVarshavchik emplace won't work for the same reason the `std::in_place_t` constructor overloads don't work. – rubenvb Apr 28 '21 at 12:13

1 Answers1

16

If you can use C++20, then what you want is

std::optional<Things>{std::in_place, "jadda", "neida"};

as seen in this live example. The reason you need C++20 is that the std::in_place_t constructor uses the form of

T(std::forward<Args>(args)...)

to initialize the object, but () only works for classes that have a constructor, which Things does not. C++ was updated to fix this, and that change made it into C++20.

In C++17 you can get this code to work by providing a constructor for Things that will initialize the members. That would look like

struct Things
{
    Things(const char* msg1, const char* msg2) : one(msg1), two(msg2) {}
    Unmovable one;
    Unmovable two;
};

int main()
{
    std::optional<Things>{std::in_place, "jadda", "neida"};
}

and you can see that working in this live example


In case you were curious, the new language added to handle this in C++20 can be found in [dcl.init.general]/15.6.2.2

NathanOliver
  • 171,901
  • 28
  • 288
  • 402