The (probably not C++14, probably Library TS) facility make_optional
is defined (in n3672) as:
template <class T>
constexpr optional<typename decay<T>::type> make_optional(T&& v) {
return optional<typename decay<T>::type>(std::forward<T>(v));
}
Why is it necessary to transform the type T
(i.e. not to just return optional<T>
), and is there a philosophical (as well as practical) justification for using decay
specifically as the transformation?
A general purpose of decay
is to take a type and modify it to be suitable for storage.
Take a look at these examples that decay
makes work, while remove_reference
would not:
auto foo( std::string const& s ) {
if (global_condition)
return make_optional( s );
else
return {};
}
or
void function() { std::cout << "hello world!\n"; }
auto bar() { return std::make_optional( function ); }
or
int buff[15];
auto baz() { return std::make_optional( buff ); }
An optional<int[15]>
would be a very strange beast -- C style arrays do not behave well when treated like literals, which is what optional
does to its parameter T
.
If you are making a copy of data, the const
or volatile
nature of the source does not matter. And you can only make simple copy of arrays and functions by decaying them to pointers (without falling back on std::array
or similar). (in theory, work could be done to make optional<int[15]>
work, but it would be lots of extra complications)
So std::decay
solves all of these issues, and does not really cause problems, so long as you allow make_optional
to deduce its argument's type instead of passing T
literally.
If you want to pass in a T
literally, there is no reason to use make_optional
after all.
This is not new behaviour with make_optional
; the utility functions make_pair
and make_tuple
behave in the same way. I can see at least two reasons to do this:
It may be undesirable or impossible to actually instantiate the template with the undecayed types.
If T
is a function type, well, you just can't store a function inside a class, period; but you can store a function pointer (the decayed type).
If T
is an array type: the resulting class is going to be "defective" because it can't be copied, owing to the fact that arrays are not copy-assignable. For optional
the value_or
member function can't be compiled at all, because it returns T
. Consequently you can't have an array type in an optional
at all.
Not decaying the types may lead to unexpected behaviour.
If the argument is a string literal, I personally would expect the type contained to be const char*
rather than const char [n]
. This decay occurs in most places, so why not here?
If v
is an lvalue then T
will be deduced as an lvalue reference type. Do I actually want a pair containing a reference, for instance, just because one of the arguments was an lvalue? Surely not.
The type inside the pair or tuple or optional or whatever shouldn't acquire the cv-qualification of v
. That is, say we have x
declared as const int
. Should make_optional(x)
create a optional<const int>
? No it shouldn't; it should create optional<int>
.
Just look at what decay
does:
Removes reference qualifiers—T&
→ T
; no optional references, else you couldn’t reseat the optional
, which needs to be assignable.
For array types, removes extent—T[42]
→ T*
; optional fixed-extent arrays aren’t that useful because each array size has a different type, and you can’t directly pass array types by value, necessary for the value
and value_or
member functions to work.
For function types, adds pointer—T(U)
→ T(*)(U)
; no optional function references, for similar reasons.
Otherwise, removes cv-qualifiers—const int
→ int
; no optional const
values, again, else you couldn’t reseat the optional
.
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