What I'm talking about
The overloads I'm referring to are 3 and 4 at std::unique_ptr<T,Deleter>::unique_ptr
, which have this signature:
unique_ptr( pointer p, /* see below */ d1 ) noexcept;
My question(s)
Mainly these:
- What does the explanation of
/* see below */
actually mean? - How do I make use of it, as a programmer, when choosing what to pass as a deleter type template argument to
std::unique_ptr
?
But also, more in detail:
- Is the fact that the constructor of
std::unique_ptr
is templated the reason why the deleter template argument must be provided? - If the answer to the preceding quetion is affirmative, then what does the sentence The program is ill-formed if either of these two constructors is selected by class template argument deduction from the linked page mean?
- How can
_Dp
and_Del
actually differ, and how is this important?
My unsuccessful attempt to get my head around it
Here I try to explain my reasoning. Some of the question anticipated above are scattered in the text too.
My understanding is that in before C++17, template type deduction does not apply to classes, but only to functions, so when creating an instance of template class, such as std::unique_ptr
, all mandatory (i.e. with no = default_type_or_value
) template arguments of the template class must be provided via <…>
.
Furthermore, in /usr/include/c++/10.2.0/bits/unique_ptr.h
, I see more or less this:
namespace std {
// …
template <typename _Tp, typename _Dp = default_delete<_Tp>>
class unique_ptr {
public:
// …
using deleter_type = _Dp;
// …
template<typename _Del = deleter_type, typename = _Require<is_copy_constructible<_Del>>>
unique_ptr(pointer __p, const deleter_type& __d) noexcept : _M_t(__p, __d) { }
// …
}
// …
}
where the constructor is templated itself on the type parameter _Del
, which is defaulted to the class' deleter_type
(which is an alias for _Dp
); from this I understand, correct me if I'm wrong (*), that std::unique_ptr
cannot even take advantage of C++17's template type deduction for classes, therefore the template argument for _Dp
is still compulsory as far as this overloads are concerned (i.e. if a deleter object is to be passed as second argument to the constructor).
Since this is the case, the actual type argument that we pass to std::unique_ptr
can be adorned with reference declarators, as explained at the linked page. But this is where I get lost, not to mention that I do see that in general _Dp
and _Del
can be different (e.g. they can differ by reference declarators), which complicates my understanding even more.
However, I'll copy the bit of the page that explains the various possible scenarios:
3-4) Constructs a
std::unique_ptr
object which ownsp
, initializing the stored pointer withp
and initializing a deleterD
as below (depends upon whetherD
is a reference type)
a) If
D
is non-reference typeA
, then the signatures are:unique_ptr(pointer p, const A& d) noexcept; unique_ptr(pointer p, A&& d) noexcept;
b) If
D
is an lvalue-reference typeA&
, then the signatures are:unique_ptr(pointer p, A& d) noexcept; unique_ptr(pointer p, A&& d) = delete;
c) If
D
is an lvalue-reference typeconst A&
, then the signatures are:unique_ptr(pointer p, const A& d) noexcept; unique_ptr(pointer p, const A&& d) = delete;
In all cases the deleter is initialized from
std::forward<decltype(d)>(d)
. These overloads only participate in overload resolution ifstd::is_constructible<D, decltype(d)>::value
istrue
.
The only way I can interpret the quoted text is as follows, with a lot of doubts.
- If we want to pass a deleter
d
as an argument to the constructor, we must explicity pass aD
as the template argument to... what? To theclass
and/or to its constructor? Is it even possible to pass template arguments to the constructor? - That
D
can be of three kinds- If we specify it as
A
, that means we want to be able to pass both a (possiblyconst
) lvalue or an rvalue asd
, so both overloads takingconst A&
andA&&
are defined. - If we specify it as a
, that means we want to be not able to pass an rvalue asconstA&d
, therefore the overload takingA&&
is deleted, as it would bind to rvalues, and the overloadA&
is used instad ofconst A&
, because the latter would bind to rvalues too. - If we specify it as a
const A&
, that means we want to be able to passbothan lvalueor an rvalueasd
, so the overload takingconst A&
is the one to pick, whereas the other one takingconst A&&
isdelete
d because that parameter type couldn't bind to lvalues, andit would treat rvalues not differently than, as explained in the answers, most importantly, it binds to rvalues preventing the other overload,const A&
doesconst A&
from binding to rvalues, which would result in a dangling reference being stored in thestd::unique_ptr
(the reason for this is here).
- If we specify it as
- However, what is the different usecase for 1. and 3. when an rvalue is passed as
d
? 1. binds it toA&&
and 3. bind it toconst A&
, so the former could steal resources and the latter couldn't.
Last but not least, the linked page also adds something specific to C++17:
The program is ill-formed if either of these two constructors is selected by class template argument deduction.
which is not clear at all to me, in light of my understanding (see (*) above): how could type deduction happen for these constructors?
So the bottom line question is: how is this complexity in the way std::unique_ptr<T,Deleter>::unique_ptr
is declared useful to me as a programmer?