30

Assume namespace std throughout.

The C++14 committee draft N3690 defines std::make_unique thus:

[n3690: 20.9.1.4]: unique_ptr creation    [unique.ptr.create]

template <class T, class... Args> unique_ptr<T> make_unique(Args&&... args);

1 Remarks: This function shall not participate in overload resolution unless T is not an array.
2 Returns: unique_ptr<T>(new T(std::forward<Args>(args)...)).

template <class T> unique_ptr<T> make_unique(size_t n);

3 Remarks: This function shall not participate in overload resolution unless T is an array of unknown bound.
4 Returns: unique_ptr<T>(new typename remove_extent<T>::type[n]()).

template <class T, class... Args> unspecified make_unique(Args&&...) = delete;

5 Remarks: This function shall not participate in overload resolution unless T is an array of known bound.

Now, this seems to me to be about as clear as mud, and I think it needs more exposition. But, this editorial comment aside, I believe I've decoded the meanings of each variant:

  1. template <class T, class... Args> unique_ptr<T> make_unique(Args&&... args);

    Your bog-standard make_unique for non-array types. Presumably the "remark" indicates that some form of static assertion or SFINAE trick is to prevent the template from being successfully instantiated when T is an array type.

    At a high-level, see it as the smart-pointer equivalent to T* ptr = new T(args);.

  2. template <class T> unique_ptr<T> make_unique(size_t n);

    A variant for array types. Creates a dynamically-allocated array of n × Ts, and returns it wrapped in a unique_ptr<T[]>.

    At a high-level, see it as the smart-pointer equivalent to T* ptr = new T[n];.

  3. template <class T, class... Args> unspecified make_unique(Args&&...)

    Disallowed. "unspecified" would probably be unique_ptr<T[N]>.

    Would otherwise be the smart-pointer equivalent to something like the invalid T[N]* ptr = new (keep_the_dimension_please) (the_dimension_is_constexpr) T[N];.

First of all, am I correct? And, if so, what's going on with the third function?

  • If it's there to disallow programmers from attempting to dynamically-allocate an array while providing constructor arguments for each element (just as new int[5](args) is impossible), then that's already covered by the fact that the first function cannot be instantiated for array types, isn't it?

  • If it's there to prevent the addition to the language of a construct like T[N]* ptr = new T[N] (where N is some constexpr) then, well, why? Wouldn't it be completely possible for a unique_ptr<T[N]> to exist that wraps a dynamically-allocated block of N × Ts? Would this be such a bad thing, to the extent that the committee has gone out of its way to disallow its creation using make_unique?

Why is make_unique<T[N]> disallowed?

R. Martinho Fernandes
  • 228,013
  • 71
  • 433
  • 510
Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
  • STL (the author of this proposal) talked about it in his Channel 9 video. I think it was one of the Core C++ ones. IIRC, there's nothing about why `unique_ptr` is disallowed, though, just that this followed suit. – chris May 16 '13 at 20:35
  • _[This question originated in chat](http://chat.stackoverflow.com/transcript/message/9459388#9459388)._ – Lightness Races in Orbit May 16 '13 at 20:35
  • 5
    "This function shall **not** participate in overload resolution **unless** T is **not** an array." - yeah... – zch May 16 '13 at 20:45
  • I'm wondering why the phrasing "*This function shall not participate in overload resolution unless T is not an array.*" rather than "*This function shall participate in overload resolution only if T is not an array*". I mean logically it's fine, but isn't it needlessly intricate? – Andy Prowl May 16 '13 at 20:46
  • @AndyProwl: I agree. It seems rather... strange. Even for C++! I'm certainly not opposed to precise language, but it's formed oddly. – Lightness Races in Orbit May 16 '13 at 20:46
  • As another aside, my confusion originally stemmed from [this wording on cppreference.com](http://en.cppreference.com/w/cpp/memory/unique_ptr/make_unique): " Construction of arrays of known bound is disallowed" Though the intent is to talk about the template argument `T` in `unique_ptr` for array-types `T`, as written the statement appears to be patently incorrect. Indeed, construction of arrays of _unknown_ bound is a physical impossibility! – Lightness Races in Orbit May 16 '13 at 20:48
  • `unique_ptr` is not illegal, only `make_unique`. – zch May 16 '13 at 20:56
  • @zch, Oh, that might make my comment a little bit more valuable then. I honestly can't remember what he said about it, just that he mentioned the reasoning for the extra check he included when presenting his `make_unique` function in the video. – chris May 16 '13 at 21:05
  • 2
    I think the answer is [here](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3588.txt) – Andy Prowl May 16 '13 at 21:06
  • Let's not make a `C++MN` tag for every single year `MN` when they produce a C++ draft... – user541686 May 16 '13 at 21:07
  • 4
    @Mehrdad: Not a draft, but a new Standard... – Andy Prowl May 16 '13 at 21:08
  • @LightnessRacesinOrbit, If you're interested, after many "meow"s, I found the video: http://channel9.msdn.com/Series/C9-Lectures-Stephan-T-Lavavej-Core-C-/STLCCSeries6 – chris May 16 '13 at 21:09
  • @AndyProwl, I think it's at 1:01:00. – chris May 16 '13 at 21:11
  • 2
    @Mehrdad: I agree! Fortunately your proposal is easy to follow, since nobody's been doing that so far. – Lightness Races in Orbit May 16 '13 at 21:12
  • @LightnessRacesinOrbit, It's kind of too bad it got removed last time it was created. I had it match the C++11 tag wiki, with a list of approved proposals. I'll bet some sort of list of new and changed features is going to have to work its way on there again. – chris May 16 '13 at 21:18
  • @chris: Who removed it and why? >. – Lightness Races in Orbit May 16 '13 at 21:19
  • @LightnessRacesinOrbit, No clue. I *know* it was soon after that big list of accepted proposals appeared (Bristol, was it?) that I did that, though. Anyway, after being told off just recently, this might be better in chat if it was to be continued. – chris May 16 '13 at 21:21
  • @AndyProwl: *"Not a draft, but a new Standard..."* they published a new standard already? – user541686 May 16 '13 at 21:29
  • @Mehrdad: No, but C++14 is going to be a new Standard. We didn't create the C++14 tag because a new draft was published, and we won't create another tag when the next draft of C++14 will be published. We created the tag because there is one official draft now. – Andy Prowl May 16 '13 at 21:30
  • And by "we" I actually mean @LightnessRacesinOrbit, but personally I agree with that decision – Andy Prowl May 16 '13 at 21:30
  • @AndyProwl: FWIW I didn't _create_ anything :) The tag already existed, though as it turns out only through one existing question, since the SO tag police replaced the committee term C++14 with the made-up term C++1y by mandate. – Lightness Races in Orbit May 16 '13 at 21:33
  • @LightnessRacesinOrbit: Oops, sorry then, that's what I inferred while peeking into the chat - my bad. I withdraw my comment ;) – Andy Prowl May 16 '13 at 21:34
  • 2
    Instead of `T[N]* ptr = new (keep_the_dimension_please) (the_dimension_is_constexpr) T[N]` you would write, in not-quite conformant code: `T (*ptr)[N] = reinterpret_cast(new T[N]);` The non-conformance is because, although there's a special rule that says you can convert a pointer to the first element of a standard-layout struct to a pointer to a struct, there's no similar rule for arrays. This code is portable across real implementations, however. – bames53 May 16 '13 at 22:02
  • 1
    I had most of this question written up about a week or two ago (whenever I read the `make_unique` proposal to the language), but was waiting until I had time to finish watching STL's video. I finally did yesterday, and now I see you beat me to it! +1 – David Stone May 26 '13 at 00:46
  • 2
    Regarding the triple negative - I filed https://github.com/cplusplus/draft/issues/86 about that. Curiously, the Standard never mentions SFINAE by that name. When the Library wants to mandate constrained overloads, it uses the "shall not participate in overload resolution unless" remark, which directs implementers to use enable_if or equivalent mechanisms. The remark is weird, but necessary to avoid mentioning SFINAE/enable_if. However, the double negative is completely unnecessary, and can be fixed editorially. – Stephan T. Lavavej Jun 02 '13 at 12:09

2 Answers2

37

Quoting from the original proposal:

T[N]

As of N3485, unique_ptr doesn't provide a partial specialization for T[N]. However, users will be strongly tempted to write make_unique<T[N]>(). This is a no-win scenario. Returning unique_ptr<T[N]> would select the primary template for single objects, which is bizarre. Returning unique_ptr<T[]> would be an exception to the otherwise ironclad rule that make_unique<something>() returns unique_ptr<something>. Therefore, this proposal makes T[N] ill-formed here, allowing implementations to emit helpful static_assert messages.

The author of the proposal, Stephan T. Lavavej, illustrates this situation in this video on Core C++ (courtesy of chris), starting from minute 1:01:10 (more or less).

Community
  • 1
  • 1
Andy Prowl
  • 124,023
  • 23
  • 387
  • 451
  • 3
    Btw: [direct link to 1:01:10 in video](http://channel9.msdn.com/Series/C9-Lectures-Stephan-T-Lavavej-Core-C-/STLCCSeries6#time=1h1m10s) (you can tweak the end of the URL) – gx_ Nov 12 '13 at 11:08
  • So... add a specialisation for `unique_ptr` ? – fwyzard Sep 06 '22 at 11:25
0

To me the third overload looks superfluous as does not change the fact that the other overloads won't match T[N] and it does not seem to help to generate better error messages. Consider the following implementation:

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 ]() );
}

When you try to call std::make_unique<int[1]>(1), the error message lists both candidates as disabled by enable_if. If you add the third, deleted overload, the error message lists three candidates instead. Also, since it is specified as =delete;, you can not provide a more meaningful error message in the third overload's body, e.g., static_assert(sizeof(T)==0,"array of known bound not allowed for std::make_shared");.

Here's the live example in case you want to play with it.

The fact that the third overload ended up in N3656 and N3797 is probably due to the history of how make_unique was developed over time, but I guess only STL can answer that :)

Daniel Frey
  • 55,810
  • 13
  • 122
  • 180
  • To the commenter that deleted the comment: The error messages varies with the compiler, but it seems to be best for GCC/Clang to have a third overload with a `static_assert` to get [short and to-the-point error messages](http://coliru.stacked-crooked.com/a/efcd9676da67112d). – Daniel Frey Dec 01 '13 at 16:12
  • 1
    I played with the live example. With the 3 overloads: `error: call to deleted function 'make_unique'` (+ a “candidate function (...) has been explicitly deleted” note + 2 “candidate template ignored: disabled by 'enable_if'” notes). With only the first 2 overloads: `error: no matching function for call to 'make_unique'`, which I find more confusing (+ 2 “candidate template ignored: disabled by 'enable_if'” notes). (And that's for clang++ (3.4); with g++ (4.8), when you remove the third overload, in fact the error message becomes *longer*.) Now a `static_assert` could indeed be even better. – gx_ Dec 01 '13 at 16:16
  • @gx_ I wonder if the standard should just allow implementations to provide additional overloads that are either deleted or have an appropriate `static_assert` in general to improve error messages - not just in this specific example, but everywhere... – Daniel Frey Dec 01 '13 at 16:19
  • @gx_ ...thinking about it: Wouldn't 17.6.5.4 [global.functions]/2 already allow this? So providing the third overload with a body containing a suitable `static_assert` would be legal even if not explicitly specified? Or does "behave as if" applies to error-scenarios as well? – Daniel Frey Dec 01 '13 at 16:37
  • 1
    *Second thought about `static_assert`:* If you replace your live examples' main body with something like `decltype( experimental::make_unique(42) ) * p = nullptr;`, then with the `static_assert` alternative it would compile without error, whereas with `=delete` (or when removing the third overload) it causes the expected error. _Edit:_ So regarding 17.6.5.4, it seems that it would be an observable difference of behavior – gx_ Dec 01 '13 at 16:38
  • 1
    @gx_ Note that 17.6.5.4/2 explicitly starts with **"A *call* to..."**, see also footnote 187) which clarifies that a non-call/unevaluated context may see additional overloads. That said, explicitly specifying the third overload for `make_unique` seems counter-productive. – Daniel Frey Dec 01 '13 at 21:43