24

When you have class template argument deduction available from C++17, why can't you deduce the template arguments of std::unique_ptr? For example, this gives me an error:

std::unique_ptr smp(new D);

That says "Argument list of class template is missing".

Shouldn't the template arguments (at least the pointer type) be deducable?

See this:

any declaration that specifies initialization of a variable and variable template

Deduplicator
  • 44,692
  • 7
  • 66
  • 118
That Guy
  • 2,349
  • 2
  • 12
  • 18
  • 1
    Not an answer, but just wondering why you are not using `make_unique` in the first place..? – Jesper Juhl Jun 29 '18 at 21:47
  • @Someprogrammerdude C++17 adds class template deduction guides that use the constructor call to deduce the template types of the class. – NathanOliver Jun 29 '18 at 21:49
  • 1
    @Someprogrammerdude Then isn't the cited phrasing a little misleading from cppreference? (about to update question with the citation :) ) – That Guy Jun 29 '18 at 21:49
  • @JesperJuhl For me, I want to use the struct aggregate initializer. `make_unique` does not seem to support it. – Yongwei Wu Apr 24 '20 at 07:25

3 Answers3

27

Lets look at new int and new int[10]. Both of those return an int*. There is no way to tell if you should have unique_ptr<int> or unique_ptr<int[]>. That right there is enough not to provide any sort of deduction guide.

NathanOliver
  • 171,901
  • 28
  • 288
  • 402
  • 3
    I see the rationale, but isn't there some rule for this? Like, you have to explicitly define the template arguments if.... – That Guy Jun 29 '18 at 21:56
  • 7
    Oh, so make it default to use `unique_ptr` and if you want the array for then you have to explicitly do so? I would think that would be more bothersome to remember then to just always tell it what you want. – NathanOliver Jun 29 '18 at 21:59
  • If that is the only intent, then better to have one more class like `std::unique_array_ptr` and have more convenient `std::unique_ptr`, rather than have something that is not convenient to use – Dmytro Ovdiienko Mar 18 '20 at 16:06
  • @DmytroOvdiienko what's inconvenient about `auto foo = make_unique(some_initializer)`? – NathanOliver Mar 18 '20 at 16:09
  • I need `std::unique_ptr` to track my pointer to array. `std::make_unique` initializes array with zeros. `std::make_unique_for_overwrite` will be available in C++20. Thus, if I want to create `unique_ptr`, I have to write type two times `std::unique_ptr(new char[128])` instead of `std::unique_ptr(new char[128])` – Dmytro Ovdiienko Mar 19 '20 at 17:18
  • @DmytroOvdiienko Are you trying to make some sort of buffer that you are filling with data? Any reason to not use a `std::vector` if that is the case? – NathanOliver Mar 19 '20 at 17:43
  • @NathanOliver, correct. I do not use `std::vector` because like a `std::make_unique` it also fills memory with zeros. Also `std::vector` consists of Pointer + Size + Capacity. I do not need Size and Capacity after memory is allocated and initialized. Thus, I can save 16 bytes in a CPU Cache Line. – Dmytro Ovdiienko Mar 19 '20 at 20:55
  • Surely the type of the deleter could at least be deduced when using `std::unique_ptr`'s binary constructors? – user2023370 Oct 20 '21 at 15:06
16

I'm not going to repeat the rationale in @NathanOliver's great answer, I'm just going to mention the how of it, the mechanics, which is what I think you are also after. You are right that if the constructor of unique_ptr looked merely like...

explicit unique_ptr( T* ) noexcept;

... it'd be possible to deduce T. The compiler generated deduction guide would work just fine. And that would be a problem, like Nathan illustrates. But the constructor is specified like this...

explicit unique_ptr( pointer p ) noexcept;

... where the alias pointer is specified as follows:

pointer : std::remove_reference<Deleter>::type::pointer if that type exists, otherwise T*. Must satisfy NullablePointer.

That specification essentially means that pointer must be an alias to __some_meta_function<T>::type. Everything on the left of ::type is a non-deduced context, which is what prevents the deduction of T from pointer. That's how these sort of deduction guides could be made to fail even if pointer needed to be T* always. Just by making it a non-deduced context will prevent the viability of any deduction guide produced from that constructor.

StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
0

So this is a side effect from those olden times at the beginning of C++, when the standard makers decided to have two different delete and delete[] operators for pointers to objects and pointers to arrays of objects.

In these modern times of C++, where we have templates (they weren't there from the beginning), std::array (for fixed sized arrays), inititalizer lists (for static fixed sized arrays) and std::vector (for dynamically sized arrays), almost nobody will need the delete[] operator anymore. I have never used it, and I wouldn't be surprised, if the vast majority of the readers of this question have not used it, either.

Removing int* array = new int[5]; in favour of auto* array = new std::array<int, 5>; would simplify things and would enable safe conversion of pointers to std::unique_ptr and std::shared_ptr. But it would break old code, and so far, the C++ standard maintainers have been very keen on backwards compatibility.

Nobody stops you, though, from writing a small inlined templated wrapper function:

template<typename T>
std::unique_ptr<T> unique_obj_ptr(T* object) {
    static_assert(!std::is_pointer<T>::value, "Cannot use pointers to pointers here");
    return std::unique_ptr<T>(object);
}

Of course, you can also create a similiar function shared_obj_ptr() to create std::shared_ptrs, and if you really need them, you can also add unique_arr_ptr() and shared_arr_ptr().

Kai Petzke
  • 2,150
  • 21
  • 29
  • `new std::array<...>` does not work for dynamically sized arrays and `new std::vector<...>` wastes an allocation. – n. m. could be an AI Nov 19 '21 at 08:34
  • The replacement for `auto var = new TYPE[N]` (with non-constant `N`) is `auto var = std::vector(N)`. So no allocations are wasted, only the space for the currently used and allocated size of the vector inside the vector data structure is required extra. For non-trivial destructors, one of these two values, the array size, is also kept around in a hidden field for proper destruction by operator `delete []`. If wasting the other eight bytes for the current size worries you, then please add non-resizeable vectors to the standard library. – Kai Petzke Nov 23 '21 at 08:26
  • `auto var = std::vector(N)` is essentially a unique array. There is no shared or non-owning counterpart. – n. m. could be an AI Nov 23 '21 at 09:14