Pusty std::optional nie jest pusty

Pusty std::optional nie jest pusty
Prav
  • Rejestracja:ponad 9 lat
  • Ostatnio:ponad 6 lat
  • Postów:25
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.

Kopiuj
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:

Kopiuj
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

edytowany 2x, ostatnio: Prav
kq
Moderator C/C++
  • Rejestracja:prawie 12 lat
  • Ostatnio:2 minuty
  • Lokalizacja:Szczecin
0

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

Kopiuj
std::optional.has_value(): false

Prav
  • Rejestracja:ponad 9 lat
  • Ostatnio:ponad 6 lat
  • Postów:25
0

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

kq
Moderator C/C++
  • Rejestracja:prawie 12 lat
  • Ostatnio:2 minuty
  • Lokalizacja:Szczecin
4

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

Requires: *this contains a value.

UB to UB.


Prav
  • Rejestracja:ponad 9 lat
  • Ostatnio:ponad 6 lat
  • Postów:25
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ź :)

edytowany 1x, ostatnio: Prav
kq
Moderator C/C++
  • Rejestracja:prawie 12 lat
  • Ostatnio:2 minuty
  • Lokalizacja:Szczecin
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:

Kopiuj
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.


Zobacz pozostałe 2 komentarze
YU
@kq: Czy nie jest prawdą, że taka implementacja łamie strict aliasing ze względu na Core Issue 1776 dla T mającego reference/const reference member-a? Chyba chcieli to naprawić std::launder-em ale z tego co widzę z-rejectowali. Ref: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n3903.html#FI15
kq
To miało tylko obrazować jak mniej więcej to wygląda, nie twierdzę że to jest w pełni koszerna implementacja optionala. Ale chętnie się wczytam, bo użycie aligned storage wydaje mi się poprawne.
kq
Dobra, przeczytałem jeszcze raz. Status issue 1776 odpowiedzialnego za to jest CD4, czyli w C++17 powinno już być ok, jeśli dobrze rozumiem
YU
@kq: ok, jasne. Nie chciałem się czepiać, po prostu byłem ciekaw stąd pytanie. Temat jest ciekawy i bolesny zarazem bo np. wpływa na core issue 2182 co z kolei ma dość nieprzyjemne konsekwencje dla std::vector-a. Ref: https://www.reddit.com/r/cpp/comments/67xpeg/is_there_an_obstacle_to_implementing_vector_in/
YU
@kq: ok, ma to sens, czyli pewnie z-rejectowali dla c++14, ale zrobili fix-a przez std::laundera w internalsach std::optional-a w C++17.

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.