Constraint on std :: optional forwarding link constructor

std::optional has 8 constructors from 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 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?

+5
source share
2 answers

Lying traits are unproductive.

Lying traits for the basic type of vocabulary are a plus.

The underlying features for the basic type of dictionary, which 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 
+5
source

I got an answer to this as soon as I posted the question (it was included in the last part of my question as editing)

The library wants to provide users with a convenient way to determine whether it is possible to build an additional file using this constructor in SFINAE situations without causing serious errors later. If this restriction was not included, then libraries might think that the optional could be built from any other type, but later finds out that it leads to a hard error.

For example, the following example illustrates a similar problem ( https://wandbox.org/permlink/XGgWgJcNJ99BBycu )

 #include <iostream> #include <string> #include <vector> #include <tuple> using std::cout; using std::endl; class Something { public: Something() = default; template <typename U> Something(U&& u) : vec(std::forward<U>(u)) {} private: std::vector<int> vec; }; template <typename U, typename = std::void_t<>> class SomethingConstructibleFrom : public std::false_type {}; template <typename U> class SomethingConstructibleFrom< U, std::void_t<decltype(Something{std::declval<U>()})>> : public std::true_type {}; int main() { if constexpr (SomethingConstructibleFrom<std::string>::value) { // this must be constructible because the above returned true // but SURPRISE! auto something = Something{std::string{"qwerty"}}; std::ignore = something; } } 
+1
source

Source: https://habr.com/ru/post/1274311/


All Articles