std::optional::optional(3)

πŸ“¦ version 2024.07.05, in nixpkgs unstable, opensuse-leap 16.0

πŸ’… std::optional::optional(3) πŸ’…

okay so we're in the C++ Standard Libary section (yes the page spells it like that, we do not fix, we preserve 🫢), and today's main character is std::optional::optional β€” the constructor of everybody's favourite "maybe there's a value, maybe there isn't" type. bestie. this one is long. hydrate first. πŸ’–


✨ Synopsis ✨

constexpr optional() noexcept;                                  (1) (since C++17)
constexpr optional( std::nullopt_t ) noexcept;                  (1) (since C++17)
constexpr optional( const optional& other );                    (2) (since C++17)
constexpr optional( optional&& other )
    noexcept(/* see below */);                                  (3) (since C++17)

template < class U >
optional( const optional<U>& other );                           (4) (until C++20)
                                                                    (conditionally explicit)
template < class U >
constexpr optional( const optional<U>& other );                 (4) (since C++20)
                                                                    (conditionally explicit)

template < class U >
optional( optional<U>&& other );                                (5) (until C++20)
                                                                    (conditionally explicit)
template < class U >
constexpr optional( optional<U>&& other );                      (5) (since C++20)
                                                                    (conditionally explicit)

template< class... Args >
constexpr explicit optional( std::in_place_t, Args&&... args ); (6) (since C++17)

template< class U, class... Args >
constexpr explicit optional( std::in_place_t,
                             std::initializer_list<U> ilist,
                             Args&&... args );                  (7) (since C++17)

template < class U = T >
constexpr optional( U&& value );                                (8) (since C++17)
                                                                    (conditionally explicit)
ngl the original page's column layout got absolutely obliterated by the typesetter 😭 the numbers above are the ones the explanation list uses, so keep them glued to their constructors like they're your emotional support stickers. πŸŽ€

🌸 Description 🌸

Constructs a new optional object. that's the whole vibe. but how it constructs it? eight whole ways. let's go. πŸ’…

1) Constructs an object that does not contain a value. (the "nope, empty, nothing in here" era ✨ β€” this covers both the constexpr optional() noexcept; and the constexpr optional( std::nullopt_t ) noexcept; forms.)

2) Copy constructor: If other contains a value, initializes the contained value as if direct-initializing (but not direct-list-initializing) an object of type T with the expression *other. If other does not contain a value, constructs an object that does not contain a value.

  • This constructor is defined as deleted if std::is_copy_constructible_v<T> is false. πŸ’€
  • It is a trivial constructor if std::is_trivially_copy_constructible_v<T> is true.

3) Move constructor: If other contains a value, initializes the contained value as if direct-initializing (but not direct-list-initializing) an object of type T with the expression std::move(*other) and does not make other empty: a moved-from std::optional still contains a value, but the value itself is moved from. If other does not contain a value, constructs an object that does not contain a value.

this is the one people get wrong constantly, so say it with me: the optional isn't emptied, the value inside is the thing that got moved from. the box stays. the contents are haunted. πŸ‘»
  • This constructor does not participate in overload resolution unless std::is_move_constructible_v<T> is true.
  • It is a trivial constructor if std::is_trivially_move_constructible_v<T> is true.

4) Converting copy constructor: If other does not contain a value, constructs an optional object that does not contain a value. Otherwise, constructs an optional object that contains a value, initialized as if direct-initializing (but not direct-list-initializing) an object of type T with the expression *other.

  • This constructor does not participate in overload resolution unless the following conditions are met:
    • std::is_constructible_v<T, const U&> is true.
    • If T is not (possibly cv-qualified) bool, T is not constructible or convertible from any expression of type (possibly const) std::optional<U>, i.e., the following 8 values are all false:
      • std::is_constructible_v<T, std::optional<U>&>
      • std::is_constructible_v<T, const std::optional<U>&>
      • std::is_constructible_v<T, std::optional<U>&&>
      • std::is_constructible_v<T, const std::optional<U>&&>
      • std::is_convertible_v<std::optional<U>&, T>
      • std::is_convertible_v<const std::optional<U>&, T>
      • std::is_convertible_v<std::optional<U>&&, T>
      • std::is_convertible_v<const std::optional<U>&&, T>
  • This constructor is explicit if and only if std::is_convertible_v<const U&, T> is false.

5) Converting move constructor: If other does not contain a value, constructs an optional object that does not contain a value. Otherwise, constructs an optional object that contains a value, initialized as if direct-initializing (but not direct-list-initializing) an object of type T with the expression std::move(*other).

  • This constructor does not participate in overload resolution unless the following conditions are met:
    • std::is_constructible_v<T, U&&> is true.
    • If T is not (possibly cv-qualified) bool, T is not constructible or convertible from any expression of type (possibly const) std::optional<U>, i.e., the following 8 values are all false:
      • std::is_constructible_v<T, std::optional<U>&>
      • std::is_constructible_v<T, const std::optional<U>&>
      • std::is_constructible_v<T, std::optional<U>&&>
      • std::is_constructible_v<T, const std::optional<U>&&>
      • std::is_convertible_v<std::optional<U>&, T>
      • std::is_convertible_v<const std::optional<U>&, T>
      • std::is_convertible_v<std::optional<U>&&, T>
      • std::is_convertible_v<const std::optional<U>&&, T>
  • This constructor is explicit if and only if std::is_convertible_v<U&&, T> is false.

6) Constructs an optional object that contains a value, initialized as if direct-initializing (but not direct-list-initializing) an object of type T from the arguments std::forward<Args>(args)....

  • If the selected constructor of T is a constexpr constructor, this constructor is a constexpr constructor.
  • The function does not participate in the overload resolution unless std::is_constructible_v<T, Args...> is true.

7) Constructs an optional object that contains a value, initialized as if direct-initializing (but not direct-list-initializing) an object of type T from the arguments ilist, std::forward<Args>(args)....

  • If the selected constructor of T is a constexpr constructor, this constructor is a constexpr constructor.
  • The function does not participate in the overload resolution unless std::is_constructible_v<T, std::initializer_list<U>&, Args...> is true.

8) Constructs an optional object that contains a value, initialized as if direct-initializing (but not direct-list-initializing) an object of type T with the expression std::forward<U>(value).

  • If the selected constructor of T is a constexpr constructor, this constructor is a constexpr constructor.
  • This constructor does not participate in overload resolution unless the following conditions are met:
    • std::is_constructible_v<T, U&&> is true.
    • std::decay_t<U> (until C++20) / std::remove_cvref_t<U> (since C++20) is neither std::in_place_t nor std::optional<T>.
    • If T is (possibly cv-qualified) bool, std::decay_t<U> (until C++20) / std::remove_cvref_t<U> (since C++20) is not a specialization of std::optional.
  • This constructor is explicit if and only if std::is_convertible_v<U&&, T> is false.

πŸ¦‹ Parameters πŸ¦‹

  • other β€” another optional object whose contained value is copied
  • value β€” value with which to initialize the contained value
  • args... β€” arguments with which to initialize the contained value
  • ilist β€” initializer list with which to initialize the contained value

πŸŽ€ Exceptions πŸŽ€

2) Throws any exception thrown by the constructor of T.

3) Throws any exception thrown by the constructor of T. Has the following noexcept specification:

noexcept(std::is_nothrow_move_constructible<T>::value)

4-8) Throws any exception thrown by the constructor of T.


πŸ’œ Deduction guides πŸ’œ

the original page literally just says the words Deduction guides and then… nothing. empty. a whole std::nullopt of a section πŸ™ˆ the example below does use one though, so go peek at o6 if you're nosy. iykyk.


⭐ Notes ⭐

Before the resolution of LWG issue 3836, constructing an std::optional<bool> from std::optional<U> would select overload (8) instead of overloads (4,5) if U is not bool. This is because overloads (4,5) did not participate in overload resolution if T (bool in this case) can be constructed or converted from std::optional<U>, but std::optional::operator bool makes the conversion possible for any U.

As a result, the constructed std::optional<bool> always contains a value. That value is determined by whether the provided std::optional<U> object contains a value, rather than the bool value direct-initialized from the contained value:

std::optional<bool> op_false(false);
std::optional<int> op_zero(0);

std::optional<int> from_bool(op_false); // OK: contains 0 (initialized from false)
std::optional<bool> from_int(op_0);     // DEFECT (LWG 3836): contains true
                                        // because op_0 contains a value, even if
                                        // initializing bool from that value gives false
🫢 real talk: yes, the snippet above genuinely says op_0 while the declaration above it says op_zero β€” we are copying the page character for character, not fixing it, that's the whole deal. the bug being described is LWG 3836, and this naming thing is a whole separate little gremlin. πŸ’€

πŸ”₯ Example πŸ”₯

// Run this code

#include <iostream>
#include <optional>
#include <string>

int main()
{
    std::optional<int> o1, // empty
                       o2 = 1, // init from rvalue
                       o3 = o2; // copy-constructor

    // calls std::string( initializer_list<CharT> ) constructor
    std::optional<std::string> o4(std::in_place, {'a', 'b', 'c'});

    // calls std::string( size_type count, CharT ch ) constructor
    std::optional<std::string> o5(std::in_place, 3, 'A');

    // Move-constructed from std::string using deduction guide to pick the type

    std::optional o6(std::string{"deduction"});

    std::cout << *o2 << ' ' << *o3 << ' ' << *o4 << ' ' << *o5  << ' '
              << *o6 << '\n';
}

and the output is literally:

1 1 abc AAA deduction

🩹 Defect reports

okay so these behavior-changing defect reports got applied retroactively to previously published C++ standards β€” history really said β€œwe fixed it later” πŸ’€:

  • LWG 3836 β€” applied to C++17. behavior as published: when constructing an std::optional<bool> from std::optional<U>, the overload resolution would select overload (8) if U is not bool. correct behavior: always selects the converting copy/move constructor in this case ✨
  • P0602R4 β€” applied to C++17. behavior as published: copy/move constructors might not be required to propagate triviality even if underlying constructor is trivial. correct behavior: required to propagate triviality 🫢
  • P2231R1 β€” applied to C++20. behavior as published: converting constructors from another std::optional was not constexpr while the required operations can be in C++20. correct behavior: made constexpr πŸ”₯

πŸ‘€ See also

  • make_optional β€” creates an optional object (C++17) (function template)

🏷️ Category:

  • conditionally noexcept

source/version: http://cppreference.com, version 2024.06.10, page std::optional::optional(3) β€” mwah, bye bestie πŸ’–

πŸ“¦ where this page comes from