Почему `make_unique <T [N]>` запрещено?
Предположим, что существует пространство имен std
.
Проект комитета N3690 Комитета С++ 14 определяет std::make_unique
таким образом:
[n3690: 20.9.1.4]:
unique_ptr
создание [unique.ptr.create]
template <class T, class... Args> unique_ptr<T> make_unique(Args&&... args);
1 Примечания: Эта функция не должна участвовать в разрешении перегрузки, если T
не является массивом.
2 Возвращает: unique_ptr<T>(new T(std::forward<Args>(args)...)).
template <class T> unique_ptr<T> make_unique(size_t n);
3 Примечания: Эта функция не должна участвовать в разрешении перегрузки, если T
не является массивом неизвестной границы.
4 Возвращает: unique_ptr<T>(new typename remove_extent<T>::type[n]()).
template <class T, class... Args> unspecified make_unique(Args&&...) = delete;
5 Примечания: Эта функция не должна участвовать в разрешении перегрузки, если T
не является массивом известной границы.
Теперь мне кажется, что это примерно так же ясно, как грязь, и я думаю, что для этого нужно больше изложения. Но этот редакционный комментарий в стороне, я считаю, что я расшифровал значения каждого варианта:
-
template <class T, class... Args> unique_ptr<T> make_unique(Args&&... args);
Стандарт вашего болота make_unique
для типов без массива. Предположительно, "примечание" указывает, что некоторая форма статического утверждения или трюк SFINAE заключается в том, чтобы предотвратить успешное создание шаблона, когда T
является типом массива.
На высоком уровне см. это как смарт-указатель, эквивалентный T* ptr = new T(args);
.
-
template <class T> unique_ptr<T> make_unique(size_t n);
Вариант для типов массивов. Создает динамически выделенный массив n
& times; Ts
и возвращает его в unique_ptr<T[]>
.
На высоком уровне см. его как смарт-указатель, эквивалентный T* ptr = new T[n];
.
-
template <class T, class... Args> unspecified make_unique(Args&&...)
Недопустимое. "неуказанный", вероятно, будет unique_ptr<T[N]>
.
В противном случае был бы умным указателем, эквивалентным чем-то вроде недействительного T[N]* ptr = new (keep_the_dimension_please) (the_dimension_is_constexpr) T[N];
.
Прежде всего, я прав? И если да, то что происходит с третьей функцией?
-
Если это запретит программистам пытаться динамически распределять массив, предоставляя аргументы конструктора для каждого элемента (так же, как new int[5](args)
невозможно), то это уже покрывается тем фактом, что первая функция не может быть экземпляр для типов массивов, не так ли?
-
Если это предотвратить, чтобы добавить к языку такой конструкции, как T[N]* ptr = new T[N]
(где n
- некоторая constexpr
), то, ну, почему? Было бы невозможно, чтобы существовало unique_ptr<T[N]>
, которое обертывает динамически выделенный блок n
& times; T
s? Было бы так плохо, если бы комиссия отказалась от своего создания, используя make_unique
?
Почему make_unique<T[N]>
запрещен?
Ответы
Ответ 1
Цитата из исходное предложение:
T[N]
Начиная с N3485, unique_ptr
не предоставляет частичную специализацию для T[N]
. Тем не менее, пользователи будут сильно склонны писать make_unique<T[N]>()
. Эта это беспроигрышный сценарий. Возврат unique_ptr<T[N]>
будет выбирать первичный шаблон для отдельных объектов, что причудливо. Возврат unique_ptr<T[]>
было бы исключением из иначе, как правило, make_unique<something>()
возвращает unique_ptr<something>
. Следовательно, это предложение T[N]
плохо сформировано здесь, что позволяет реализациям испускать полезные static_assert
сообщения.
Автор предложения, Стефан Т. Лававей, иллюстрирует эту ситуацию в это видео на Core С++ (любезно предоставлено chris), начиная с минуты 1:01:10 (более или менее).
Ответ 2
Мне третья перегрузка выглядит излишней, так как не меняет того факта, что другие перегрузки не будут соответствовать T[N]
и, похоже, не помогают генерировать более эффективные сообщения об ошибках. Рассмотрим следующую реализацию:
template< typename T, typename... Args >
typename enable_if< !is_array< T >::value, unique_ptr< T > >::type
make_unique( Args&&... args )
{
return unique_ptr< T >( new T( forward< Args >( args )... ) );
}
template< typename T >
typename enable_if< is_array< T >::value && extent< T >::value == 0, unique_ptr< T > >::type
make_unique( const size_t n )
{
using U = typename remove_extent< T >::type;
return unique_ptr< T >( new U[ n ]() );
}
При попытке вызвать std::make_unique<int[1]>(1)
в сообщении об ошибке перечислены оба кандидата как отключенные на enable_if
. Если вы добавите третью, удаленную перегрузку, сообщение об ошибке отобразит вместо этого три кандидата. Кроме того, поскольку он указан как =delete;
, вы не можете предоставить более значимое сообщение об ошибке в третьем теле перегрузки, например, static_assert(sizeof(T)==0,"array of known bound not allowed for std::make_shared");
.
Здесь живой пример, если вы хотите играть с ним.
Тот факт, что третья перегрузка оказалась в N3656 и N3797, вероятно, связана с историей развития make_unique
с течением времени, но я думаю, что только STL может ответить на это:)