std::optional
has 8 constructors as of this date, listed below (also here http://en.cppreference.com/w/cpp/utility/optional/optional)
/* (1) */ constexpr optional() noexcept;
/* (1) */ constexpr optional( std::nullopt_t ) noexcept;
/* (2) */ constexpr optional( const optional& other );
/* (3) */ constexpr optional( optional&& other ) noexcept(/* see below */);
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
to be constructed from cv-ref qualified references to 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 the std::optional
instance, without having to use std::in_place
. This happens because when a curly brace enclosed argument list is passed to the constructor, the default type is used because the function template cannot deduce a type from the {}
(at least that is my understanding of the situation and is a neat trick I picked up only recently) (also note that this can only be used to call non explicit constructors of the underlying type, as per 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 constraints on the last constructor that I can understand
std::decay_t<U>
is neither std::in_place_t
nor std::optional<T>
std::is_convertible_v<U&&, T>
is falseThe first is easy to understand, it helps prevent against ambiguities with constructors (2), (3), (4), (5), (6) and (7). If the type is std::in_place
it can conflict with (6) and (7). If the type is an instantiation of std::optional
then it can conflict with (2), (3), (4) and (5).
The second just "forwards" the explicitness of the constructor of the underlying type to the optional
type
But the third restriction is curious
std::is_constructible_v<T, U&&>
is trueWhy is this needed? (8) can never conflict with the empty constructor because it needs at least one argument. That leaves only one left reason - it might conflict with std::nullopt_t
when passed std::nullopt
, but that will not happen because the nullopt
version is always a better match no matter what cv-ref qualified version of std::nullopt_t
is passed (as demonstrated 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 behind the last restriction?
Why not just let the constructor error out as usual? Is this needed to help detect if the type is constructible via an argument passed via SFINAE without causing a hard error later?
Lying traits are ungood.
Lying traits for a fundamental vocabulary type are plusungood.
Lying traits for a fundamental vocabulary type that can also easily interfere with overload resolution are doubleplusungood.
void f(std::optional<int>);
void f(std::optional<const char*>);
f({""}); // ambiguous without the constraint
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With