Pusty std::optional nie jest pusty

0

Według standardu (przynajmniej tyle zrozumiałem) std::optional<T> zainicjalizowany przy użyciu std::nullopt powinien nie tworzyć obiektu typu T, a z moich obserwacji wynika, że GCC, Clang i MSVC robią to nawet gdy T ma usunięty domyślny konstruktor.

struct TestStruct {
    int a;
    
    int test() { return std::rand() % 1000; }
    virtual void vir() {}
    virtual ~TestStruct() {}
    
private:
    TestStruct() = delete;
};

int main()
{
    std::optional<TestStruct> o{ std::nullopt };
    std::cout << std::boolalpha << "o.has_value(): " << o.has_value() << o->test() << '\n';
}


W jaki sposób optional instantyzuje klasę z usuniętym prywatnym konstruktorem?
Dlaczego optional w ogóle instantyzuje klasę mimo, że:

23.6.3.1 Constructors [optional.ctor]
constexpr optional() noexcept;
constexpr optional(nullopt_t) noexcept;
1 Postconditions: *this does not contain a value.
2 Remarks: No contained value is initialized. For every object type T these constructors shall be constexpr

działający przykład: https://wandbox.org/permlink/YfSO9WGnI2wzyZzX
standard strona 571: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/n4659.pdf

0

W jaki sposób doszedłeś do wniosku, że tworzony jest obiekt? Twój kod wypisuje:

std::optional.has_value(): false
0

Linijkę niżej wywołuję metodę printAddress na optionalu który zwrócił, że nie ma wartości.

4

https://timsong-cpp.github.io/cppwp/n4659/optional.observe#5

Requires: *this contains a value.

UB to UB.

0

Czyli w tym wypadku przez UB przy wywołaniu operator* tworzy się obiekt, którego normalnie nie dało by się stworzyć (usunięty konstruktor). Chyba jednak będę używał value().
Dzięki za odpowiedź :)

4

Nie tworzy się żaden obiekt. Wywołanie operatora* na pustym optionalu to UB. Zastanów się jak zdefinowany jest optional, wygląda to ± tak:

template<typename T>
struct optional
{
    using data_t = std::aligned_storage<sizeof(T), alignof(T)>::type;
    data_t data;
    bool is_valid = false;

    T& operator*() { return *reinterpret_cast<T*>(&data); }
    bool has_value() const { return is_valid; }
    auto& operator=(T const& o) { new (&data) T(o); is_valid = true; }
    ~optional() { if(is_valid) (**this).~T(); }
};

To jest pseudokod pisany w edytorze na forum, więc może nie działać, ale zasada powinna być jasna: masz jakiś bufor danych, jak nie został zainicjalizowany to próba jego odczytu jako obiektu klasy to UB. I tyle. Nic nie zostaje magicznie zainicjalizowane.

1 użytkowników online, w tym zalogowanych: 0, gości: 1