std::optional has 8 constructors from this date, listed below (also here http://en.cppreference.com/w/cpp/utility/optional/optional )
constexpr optional() noexcept; constexpr optional( std::nullopt_t ) noexcept; constexpr optional( const optional& other ); constexpr optional( optional&& other ) noexcept(); template < class U > /* (4) */ /* EXPLICIT */ optional( const optional<U>& other ); template < class U > /* (5) */ /* EXPLICIT */ optional( optional<U>&& other ); template< class... Args > /* (6) */ constexpr explicit optional( std::in_place_t, Args&&... args ); template< class U, class... Args > /* (7) */ constexpr explicit optional( std::in_place_t, std::initializer_list<U> ilist, Args&&... args ); template < class U = value_type > /* (8) */ /* EXPLICIT */ constexpr optional( U&& value );
I like the last constructor. It helps std::optional be built from cv-ref links for type Type . Which is super convenient.
Other than that, the last constructor also helps because it is a convenient way to use list initialization to initialize an instance of std::optional without using std::in_place . This is because when the list of structural figures in braces is passed to the constructor, the default type is used, because the function template cannot infer the type from {} (at least this is my understanding of the situation and itโs a neat trick I (also note that this can only be used to call explicit base type constructors according to the rules here http://en.cppreference.com/w/cpp/language/list_initialization )
auto optional = std::optional<std::vector<int>>{{1, 2, 3, 4}};
There are two limitations to the last constructor that I can understand
std::decay_t<U> is neither std::in_place_t nor std::optional<T>- This constructor is explicit if and only if
std::is_convertible_v<U&&, T> is false
The first is easy to understand, it helps prevent fuzziness with constructors (2), (3), (4), (5), (6) and (7). If the type is std::in_place , it may conflict with (6) and (7). If the type is an instance of std::optional , then it may conflict with (2), (3), (4) and (5).
The second one simply forwards explains the base type constructor to optional
But the third limitation is curious
- This constructor is not involved in overload resolution if
std::is_constructible_v<T, U&&> not true
Why is this needed? (8) can never conflict with an empty constructor, because it needs at least one argument. This leaves only one left-hand reason - it may conflict with std::nullopt_t when passing std::nullopt , but this will not happen because the nullopt version nullopt always better regardless of which cv-ref version std::nullopt_t (as shown below)
void func(int) { cout << __PRETTY_FUNCTION__ << endl; } template <typename U> void func(U&&) { cout << __PRETTY_FUNCTION__ << endl; } int main() { auto i = int{1}; func(1); func(i); func(std::move(i)); func(std::as_const(i)); func(std::move(std::as_const(i))); }
What is the reason for the last limitation?
Why not just leave the constructor a mistake, as usual? This is necessary in order to help determine whether the type is constructive through the argument passed through SFINAE without causing a serious error later?