Почему make_optional разлагает свой тип аргумента?
Объект (возможно, не С++ 14, возможно, библиотека TS) make_optional
определен (в n3672) как:
template <class T>
constexpr optional<typename decay<T>::type> make_optional(T&& v) {
return optional<typename decay<T>::type>(std::forward<T>(v));
}
Почему необходимо преобразовать тип T
(т.е. не просто возвращать optional<T>
), и существует ли философское (а также практическое) обоснование использования decay
в частности как преобразование?
Ответы
Ответ 1
Общая цель decay
- взять тип и изменить его, чтобы он был подходящим для хранения.
Взгляните на эти примеры, что decay
выполняет работу, а remove_reference
не будет:
auto foo( std::string const& s ) {
if (global_condition)
return make_optional( s );
else
return {};
}
или
void function() { std::cout << "hello world!\n"; }
auto bar() { return std::make_optional( function ); }
или
int buff[15];
auto baz() { return std::make_optional( buff ); }
An optional<int[15]>
был бы очень странным зверем - массивы стиля C плохо себя чувствуют, когда обрабатываются как литералы, что optional
делает с его параметром T
.
Если вы делаете копию данных, природа источника const
или volatile
не имеет значения. И вы можете сделать только простую копию массивов и функций, разложив их на указатели (не отступая от std::array
или подобных). (теоретически можно было бы работать, чтобы сделать работу optional<int[15]>
, но было бы много дополнительных осложнений)
Итак, std::decay
решает все эти проблемы и на самом деле не вызывает проблем, если вы позволяете make_optional
выводить свой тип аргумента, а не передавать T
буквально.
Если вы хотите передать T
буквально, нет никаких оснований использовать make_optional
в конце концов.
Ответ 2
Это не новое поведение с make_optional
; функции утилиты make_pair
и make_tuple
ведут себя одинаково. Для этого я вижу как минимум две причины:
Ответ 3
Просто посмотрите, что делает decay
:
-
Удаляет контрольные квалификаторы - T&
→ T
; нет дополнительных ссылок, иначе вы не смогли бы переустановить optional
, который должен быть назначен.
-
Для типов массивов удаляет экстент - T[42]
→ T*
; необязательные массивы с фиксированной степенью сложности, которые полезны, потому что каждый размер массива имеет другой тип, и вы не можете напрямую передавать типы массивов по значению, необходимые для работы функций-членов value
и value_or
.
-
Для типов функций добавляет указатель- T(U)
→ T(*)(U)
; без дополнительных ссылок на функции по тем же причинам.
-
В противном случае удаляются cv-qualifiers- const int
→ int
; нет дополнительных const
значений, опять же, иначе вы не смогли бы переустановить optional
.