Topic: shared_ptr and nullptr


Author: Greg Herlihy <greghe@mac.com>
Date: Wed, 7 Nov 2007 01:07:37 CST
Raw View
On Nov 6, 5:50 pm, jgott...@carolina.rr.com (Joe Gottman) wrote:
>
> But shared_ptr's operator== is defined as follows:
>
> template<class T, class U>
> bool operator==(shared_ptr<T> const& a, shared_ptr<U> const& b);
>
> So even if we do give shared_ptr a constructor that takes a nullptr,
> will the above operator==() function get called?
>
> We might have to define the following 4 functions:
>
>     template <class T> bool operator==(const shared_ptr<T> &, nullptr_t);
>     template <class T> bool operator!=(const shared_ptr<T> &, nullptr_t);
>     template <class T> bool operator==(nullptr_t, const shared_ptr<T> &);
>     template <class T> bool operator!=(nullptr_t, const shared_ptr<T> &);

These proposed shared_ptr methods seem to be multiplying at an
alarming rate. And as I pointed out in an earlier post, the
proliferation of these routines goes well beyond replacing 0's with
nullptr's - the kind of incremental improvement that nullptr provides
best. In fact, the wholesale addition of all of these these new,
nullptr_t-specific methods - might even discourage programmers from
adopting nullptr in their one code. Because one might easily (if
mistakenly) conclude that the use of nullptr requires duplicating
existing interfaces and specializing each method exclusively for a
nullptr argument - a cost in overhead that would exceed any perceived
benefit.

Fortunately, none of these additional shared_ptrs methods are needed -
as improbable as it might at first sound - there is in fact a way for
shared_ptr's existing interface to support nullptr arguments -
practically unchanged. In fact the set of changes that would need to
be made are so subtle as to be hardly noticeable:

    // construction

    template<class Y=T> explicit shared_ptr(Y* p);
    template<class Y=T, class D> shared_ptr(Y* p, D d);

    // comparison

    template<class T, class U=T>
    bool operator==(shared_ptr<T> const& a, shared_ptr<U> const& b);

    template<class T, class U=T>
    bool operator!=(shared_ptr<T> const& a, shared_ptr<U> const& b);

    template<class T, class U=T>
    bool operator<(shared_ptr<T> const& a, shared_ptr<U> const& b);

    // ... and so forth

As it turns out, the only change needed for shared_ptr to support
nullptr arguments - is to specify the type of a pointer template type
parameter - whenever that type cannot be deduced from the argument
provided.

Greg



---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: jgottman@carolina.rr.com (Joe Gottman)
Date: Thu, 8 Nov 2007 07:18:06 GMT
Raw View
Greg Herlihy wrote:
> On Nov 6, 5:50 pm, jgott...@carolina.rr.com (Joe Gottman) wrote:

> These proposed shared_ptr methods seem to be multiplying at an
> alarming rate. And as I pointed out in an earlier post, the
> proliferation of these routines goes well beyond replacing 0's with
> nullptr's - the kind of incremental improvement that nullptr provides
> best. In fact, the wholesale addition of all of these these new,
> nullptr_t-specific methods - might even discourage programmers from
> adopting nullptr in their one code. Because one might easily (if
> mistakenly) conclude that the use of nullptr requires duplicating
> existing interfaces and specializing each method exclusively for a
> nullptr argument - a cost in overhead that would exceed any perceived
> benefit.
>
> Fortunately, none of these additional shared_ptrs methods are needed -
> as improbable as it might at first sound - there is in fact a way for
> shared_ptr's existing interface to support nullptr arguments -
> practically unchanged. In fact the set of changes that would need to
> be made are so subtle as to be hardly noticeable:
>
>     // construction
>
>     template<class Y=T> explicit shared_ptr(Y* p);
>     template<class Y=T, class D> shared_ptr(Y* p, D d);
>
>     // comparison
>
>     template<class T, class U=T>
>     bool operator==(shared_ptr<T> const& a, shared_ptr<U> const& b);
>
>     template<class T, class U=T>
>     bool operator!=(shared_ptr<T> const& a, shared_ptr<U> const& b);
>
>     template<class T, class U=T>
>     bool operator<(shared_ptr<T> const& a, shared_ptr<U> const& b);
>
>     // ... and so forth
>
> As it turns out, the only change needed for shared_ptr to support
> nullptr arguments - is to specify the type of a pointer template type
> parameter - whenever that type cannot be deduced from the argument
> provided.
>

    There are two problems with this approach that I can see:

1)  Many people want to be able to implicitly convert nullptr to
shared_ptr<T>.  The reason the regular pointer constructor is explicit
is to avoid accidentally taking ownership of a pointer, since the
pointer could well be owned by some other object or point to a local
object.  Since nullptr never points to anything this objection does not
apply to it, and it would be convenient to be able to implicitly convert
nullptr to a shared_ptr<T>, for instance by writing "return nullptr;"
inside a function that returns a shared_ptr or passing nullptr to a
shared_ptr parameter.

2)  Your proposed solution for the operator== problem,

      template<class T, class U=T>
     bool operator==(shared_ptr<T> const& a, shared_ptr<U> const& b)

    will work in the case
           shared_ptr<int> p(new int);
           p == nullptr;

However, I don't think it will work in the case
         nullptr == p.


Joe Gottman

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: Greg Herlihy <greghe@mac.com>
Date: Fri, 9 Nov 2007 13:12:29 CST
Raw View
On Nov 7, 11:18 pm, jgott...@carolina.rr.com (Joe Gottman) wrote:
>
> 1)  Many people want to be able to implicitly convert nullptr to
> shared_ptr<T>.  The reason the regular pointer constructor is explicit
> is to avoid accidentally taking ownership of a pointer, since the
> pointer could well be owned by some other object or point to a local
> object.  Since nullptr never points to anything this objection does not
> apply to it, and it would be convenient to be able to implicitly convert
> nullptr to a shared_ptr<T>, for instance by writing "return nullptr;"
> inside a function that returns a shared_ptr or passing nullptr to a
> shared_ptr parameter.

Thanks for explaining the rationale behind shared_ptr's current
constructor. It makes sense to me. So a nullptr_t overload (of
shared_ptr's) constructor is probably unavoidable. But I remain
optimistic about shared_ptr's comparison operators.

> 2)  Your proposed solution for the operator== problem,
>
>       template<class T, class U=T>
>      bool operator==(shared_ptr<T> const& a, shared_ptr<U> const& b)
>
>     will work in the case
>            shared_ptr<int> p(new int);
>            p == nullptr;
>
> However, I don't think it will work in the case
>          nullptr == p.

True. One solution would be to declare two versions of each comparison
operator - one a member function and the other a friend:

       template <class U=T>
       bool operator==( const shared_ptr<U>& rhs);

       template <class U=T>
       friend bool operator==(const shared_ptr<U>& lhs,
                              const shared_ptr& rhs);

Dividing operator==() into two methods ensures that the same two
methods will handle all shared_ptr comparisons for equality (including
comparisons with nullptr) - and do so without dedicating a method just
for nullptrs - or, indeed, without "nullptr_t" appearing anywhere in
either method's declaration.

Greg





---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: Greg Herlihy <greghe@mac.com>
Date: Fri, 2 Nov 2007 11:55:55 CST
Raw View
On Oct 31, 9:48 am, Joe Gottman <jgott...@carolina.rr.com> wrote:
>    Consider the following program:
>
>    struct Base {};
>    struct Derived : public Base {};
>    int main() {
>       shared_ptr<Base> p(nullptr); // Which constructor will be called?
>       return 0;
>     }
>
> This program will fail to compile because shared_ptr uses the following
> template constructor to construct itself from pointers:
>
> template <class Y> shared_ptr(Y *);
>
> There are two equally valid conversions for nullptr_t: Base * or Derived
> *, so the conversion from nullptr_t is ambiguous.

No, initializing a shared_ptr with a nullptr argument would always
fail - no matter whether the conversion was ambiguous or not. The type
of "T" in "T*" cannot be deduced from a nullptr argument.

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: Greg Herlihy <greghe@mac.com>
Date: Fri, 2 Nov 2007 12:40:20 CST
Raw View
On Nov 2, 10:55 am, Greg Herlihy <gre...@mac.com> wrote:
> On Oct 31, 9:48 am, Joe Gottman <jgott...@carolina.rr.com> wrote:

> >    Consider the following program:
>
> >    struct Base {};
> >    struct Derived : public Base {};
> >    int main() {
> >       shared_ptr<Base> p(nullptr); // Which constructor will be called?
> >       return 0;
> >     }
>
> > This program will fail to compile because shared_ptr uses the following
> > template constructor to construct itself from pointers:
>
> > template <class Y> shared_ptr(Y *);
>
> > There are two equally valid conversions for nullptr_t: Base * or Derived
> > *, so the conversion from nullptr_t is ambiguous.
>
> No, initializing a shared_ptr with a nullptr argument would always
> fail - no matter whether the conversion was ambiguous or not. The type
> of "T" in "T*" cannot be deduced from a nullptr argument.

Looks like the second half of my original post did not get by the
Google censors. :-) Here is the unexpurgated content:

> Proposed Resolution:
>
>     Add the following constructors to 20.6.6.2:
>
>     explicit shared_ptr(nullptr_t);
>     template <class D> shared_ptr(nullptr_t, D d, A a);
>     template <class D, class A> shared_ptr(nullptr_t, D d, A a);

It seems odd to me that shared_ptr's constructor (and other methods)
would need nullptr overloads. After all, if a pointer argument's type
is not specified, wouldn't shared_ptr<T> just naturally assume that
the type is a T pointer?

So instead of a nullptr constructor overload, I suggest adding this
(more generic) constructor:

    explicit shared_ptr(T* p)

that accepts not only T pointers - but also matches 0, NULL and
nullptr arguments as well. This constructor could be a "delegating"
constructor and do nothing more than call the current template
constructor - a constructor that neither a 0 or nullptr argument can
today invoke on its own.

Sunilarly, a T* overload of reset() and the other member function
would serve the same purpose. And by matching both NULL and nullptr -
these additional overloads would look both backwards - and forwards as
well.

Greg



---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: "Joe Gottman" <jgottman@carolina.rr.com>
Date: Mon, 13 Nov 2006 23:09:59 CST
Raw View
   If the nullptr proposal
(http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2004/n1601.pdf) is
accepted into the standard, then shared_ptr will need to be modified to work
with it.  nullptr is a special value (of type nullptr_t) that can be
converted to a null pointer of any pointer or pointer-to-member type but not
to any integral or boolean type.  It is meant as a replacement for the NULL
macro.

   It seems likely that code, especially in templates, might want to
initialize a shared_ptr<X> with nullptr.  Unfortunately, as shared_ptr is
now written this will fail because the relevant constructor for
shared_ptr<X> looks like
  template<class Y> shared_ptr(Y *p);

Since nullptr can be converted to any pointer type, there's no best match.
The obvious solution is to give shared_ptr a new constructor
    shared_ptr(nullptr_t);
so that we can construct a shared_ptr from nullptr. Naturally we would need
to define shared_ptr::reset(nullptr_t) as well.

   The things I am not sure about are
   1)  Does this constructor produce an empty shared_ptr (i.e. one that is
equivalent to a default-constructed shared_ptr) or a shared_ptr that owns a
null pointer (i.e. one that is equivalent to shared_ptr(static_cast<X
*>(0)) )?The major difference between these 2 cases is that the first one
cannot throw and does not carry use-count information while the second one
can throw and does carry use-count information.
   2)  Should this constructor be explicit?  If not, we would probably want
to define operator== and operator!= that take a shared_ptr and a nullptr_t.
   3) Would we want to have a constructor that takes a nullptr_t and a
destructor object?  I'm not sure what use one of these would be, except
maybe to simulate a Java finally block and run code when we leave a scope
for any reason.

Joe Gottman

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: "Andrei Polushin" <polushin@gmail.com>
Date: Tue, 14 Nov 2006 01:19:27 CST
Raw View
Joe Gottman wrote:
>    If the nullptr proposal
> (http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2004/n1601.pdf) is
> accepted into the standard, then shared_ptr will need to be modified to work
> with it.  nullptr is a special value (of type nullptr_t) that can be
> converted to a null pointer of any pointer or pointer-to-member type but not
> to any integral or boolean type.  It is meant as a replacement for the NULL
> macro.
>
>    It seems likely that code, especially in templates, might want to
> initialize a shared_ptr<X> with nullptr.  Unfortunately, as shared_ptr is
> now written this will fail because the relevant constructor for
> shared_ptr<X> looks like
>   template<class Y> shared_ptr(Y *p);
>
> Since nullptr can be converted to any pointer type, there's no best match.

As a consequence of wording on page 9 of N1601, the nullptr_t is
defined as if the following two definitions exist:

    typedef most_derived_t* nullptr_t;
    typedef most_base_t::*  nullptr_t;

Thus there is no ambiguity with the shared_ptr constructor

    template<class Y> shared_ptr(Y *p);

because it is matched as if Y is the most_derived_t type.


--
Andrei Polushin

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: "Peter Dimov" <pdimov@gmail.com>
Date: Tue, 14 Nov 2006 09:40:19 CST
Raw View
Joe Gottman wrote:

>    The things I am not sure about are
>    1)  Does this constructor produce an empty shared_ptr (i.e. one that is
> equivalent to a default-constructed shared_ptr) or a shared_ptr that owns a
> null pointer (i.e. one that is equivalent to shared_ptr(static_cast<X
> *>(0)) )?The major difference between these 2 cases is that the first one
> cannot throw and does not carry use-count information while the second one
> can throw and does carry use-count information.

This would depend on the semantics of nullptr, in particular, whether
Y* is deducible from it, and with what Y. If it isn't, empty is the
only reasonable option. If it is, my vote still goes for empty.

>    2)  Should this constructor be explicit?  If not, we would probably want
> to define operator== and operator!= that take a shared_ptr and a nullptr_t.

If it constructs an empty shared_ptr, it should be implicit. Users want
this:

void f( shared_ptr<X> px );

int main()
{
    f( nullptr );
}

to work.

>    3) Would we want to have a constructor that takes a nullptr_t and a
> destructor object?  I'm not sure what use one of these would be, except
> maybe to simulate a Java finally block and run code when we leave a scope
> for any reason.

No, I don't think that we want it (but we might have it anyway, in
which case we probably don't want to prohibit it, either).

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: AlbertoBarbati@libero.it (Alberto Ganesh Barbati)
Date: Tue, 14 Nov 2006 17:32:28 GMT
Raw View
Andrei Polushin ha scritto:
>
> As a consequence of wording on page 9 of N1601, the nullptr_t is
> defined as if the following two definitions exist:
>
>     typedef most_derived_t* nullptr_t;
>     typedef most_base_t::*  nullptr_t;
>
> Thus there is no ambiguity with the shared_ptr constructor
>
>     template<class Y> shared_ptr(Y *p);
>
> because it is matched as if Y is the most_derived_t type.
>
>

I'm sorry, but I don't see anything on page 9 of N1601 that could lead
to that interpretation. Could you please elaborate? What are
most_derived_t and most_base_t?

Ganesh

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: AlbertoBarbati@libero.it (Alberto Ganesh Barbati)
Date: Tue, 14 Nov 2006 19:20:55 GMT
Raw View
Peter Dimov ha scritto:
> Joe Gottman wrote:
>
>>    The things I am not sure about are
>>    1)  Does this constructor produce an empty shared_ptr (i.e. one that is
>> equivalent to a default-constructed shared_ptr) or a shared_ptr that owns a
>> null pointer (i.e. one that is equivalent to shared_ptr(static_cast<X
>> *>(0)) )?The major difference between these 2 cases is that the first one
>> cannot throw and does not carry use-count information while the second one
>> can throw and does carry use-count information.
>
> This would depend on the semantics of nullptr, in particular, whether
> Y* is deducible from it, and with what Y. If it isn't, empty is the
> only reasonable option. If it is, my vote still goes for empty.

If I understand the nullptr proposal correctly, the intent is that
nullptr shouldn't match an unconstrained T* argument. The main reason
being that there's no obvious way to deduce type T.

If that's the case, I agree that the empty ctor is preferable. I don't
see a compelling use case where you actually want to keep a use-count
for null pointers, so it's better to choose the less expensive solution.

>
>>    2)  Should this constructor be explicit?  If not, we would probably want
>> to define operator== and operator!= that take a shared_ptr and a nullptr_t.
>
> If it constructs an empty shared_ptr, it should be implicit. Users want
> this:
>
> void f( shared_ptr<X> px );
>
> int main()
> {
>     f( nullptr );
> }
>
> to work.

Never thought of that. In fact I was going to suggest to make the ctor
explicit. But your example has some beauty in it.

About operators == and !=, it would be good if tests like "if(p ==
nullptr)" and "if(p != nullptr)" were as efficient as "if(!p)" and
"if(p)" respectively, so I would add overloads, avoiding the
construction of a temporary.

>>    3) Would we want to have a constructor that takes a nullptr_t and a
>> destructor object?  I'm not sure what use one of these would be, except
>> maybe to simulate a Java finally block and run code when we leave a scope
>> for any reason.
>
> No, I don't think that we want it (but we might have it anyway, in
> which case we probably don't want to prohibit it, either).

I think it could be useful for the shared_ptr-as-scopeguard idiom. For
example:

  shared_ptr<void> guard(nullptr, cleanup);

is more readable than

  shared_ptr<void> guard(static_cast<void*>(0), cleanup);

The two-parameter reset() is less used for this idiom, but should also
be overloaded for consistency.

Just my opinion,

Ganesh

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: "Andrei Polushin" <polushin@gmail.com>
Date: Tue, 14 Nov 2006 14:50:28 CST
Raw View
Alberto Ganesh Barbati wrote:
> Andrei Polushin ha scritto:
>> As a consequence of wording on page 9 of N1601, the nullptr_t is
>> defined as if the following two definitions exist:
>>
>>     typedef most_derived_t* nullptr_t;
>>     typedef most_base_t::*  nullptr_t;
>>
>> Thus there is no ambiguity with the shared_ptr constructor
>>
>>     template<class Y> shared_ptr(Y *p);
>>
>> because it is matched as if Y is the most_derived_t type.
>
> I'm sorry, but I don't see anything on page 9 of N1601 that could lead
> to that interpretation. Could you please elaborate?

N1601 defines on page 9 that "nullptr_t is both
    a _pointer_type_ and
    a _pointer_to_member_type_".

A _pointer_ type is defined in [C++98, 8.3.1/1] (reworded by me):

    In a declaration

        T1 * D1

    the type of D1 is "pointer to T1."

A _pointer_to_member_ type is defined in [C++98, 8.3.3/1] (reworded):

    In a declaration

        T2 :: * D2

    the type of D2 is "pointer to member of type T."

Both types were defined in terms of declarations. Then if nullptr_t
is a combination of both types, it is defined in terms of both
declarations:

        T1 * D1
        T2 :: * D2

N1601 may have another intent, that was not expressed in the proposed
wording. But look at at use cases at page 7:

    // Specialization cases
    //
    template<typename T> void g( T t );
    g( 0 );                 // specializes g, T = int
    g( nullptr );           // specializes g, T = nullptr_t
    g( (float*) nullptr );  // specializes g, T = float*

    // Partial specialization cases
    //
    template<typename T> class X { };
    template<typename T> class X<T*> X { };
    template<typename T> class X<T::*> X { };
    X<nullptr_t> x; // error, ambiguous; nullptr_t is both a pointer
                    // and pointer-to-member type, so it.s undecidable
                    // which partial specialization to use

The last case is "ambiguous", and on page 5 they say that

    nullptr_t matches both a T* and a T::* partial specialization.
    If it matches two partial specializations of the same template,
    the result is ambiguous because neither partial specialization
    is more specialized than the other.

Thus the following is unambiguous:

    template<typename T> class X { };
    template<typename T> class X<T*> X { };
    X<nullptr_t> x;

I don't see how it differs from the OP question about shared_ptr.
If there is no ambiguity with X, there should be no ambiguity with
shared_ptr too.


> What are most_derived_t and most_base_t?

They are my names for T1 and T2.


--
Andrei Polushin

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: AlbertoBarbati@libero.it (Alberto Ganesh Barbati)
Date: Tue, 14 Nov 2006 22:42:56 GMT
Raw View
Andrei Polushin ha scritto:

>     // Partial specialization cases
>     //
>     template<typename T> class X { };
>     template<typename T> class X<T*> X { };
>     template<typename T> class X<T::*> X { };
>     X<nullptr_t> x; // error, ambiguous; nullptr_t is both a pointer
>                     // and pointer-to-member type, so it.s undecidable
>                     // which partial specialization to use
>
> The last case is "ambiguous", and on page 5 they say that
>
>     nullptr_t matches both a T* and a T::* partial specialization.
>     If it matches two partial specializations of the same template,
>     the result is ambiguous because neither partial specialization
>     is more specialized than the other.
>
> Thus the following is unambiguous:
>
>     template<typename T> class X { };
>     template<typename T> class X<T*> X { };
>     X<nullptr_t> x;

I believe the paper is defective because while it explicitly says that
nullptr_t matches both T* and T::*, but doesn't specify for which T!
Consider this:

  template<typename T> struct X
  {
    X() { cout << "main " << typeid(T).name() << "\n";
  };

  template<typename T> struct X<T*>
  {
    X() { cout << "specialization " << typeid(T).name() << "\n";
  };

  X<nullptr_t> x;

what should be the expected output of this code? The compiler must
instantiate the template, either from the main definition of from the
specialization. But if nullptr_t matches the specialization, the
compiler *must* deduce T. Is it void? Is it some other type? Any choice
is arbitrary. Is T nullptr_t itself? It can't be, because nullptr_t* is
a valid type.

IMHO, the only reasonable solution is that nullptr_t should *not* match
the specialization but the main definition must be chosen with T =
nullptr_t.

In the specific case of shared_ptr, you have to consider that the
constructor:

   template<class Y> shared_ptr(Y *p);

is equivalent to use a deleter object that eventually calls

   delete static_cast<Y*>(p);

and not

   delete static_cast<T*>(p);

so the choice of Y is not irrelevant.

Am I missing something?

Ganesh

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: "Andrei Polushin" <polushin@gmail.com>
Date: Wed, 15 Nov 2006 00:27:49 CST
Raw View
In context of N1601, the nullptr proposal,
Andrei Polushin wrote:
> if nullptr_t is a combination of both types, it is defined in terms
> of both declarations:
>
>         T1 * D1
>         T2 :: * D2

Alberto Ganesh Barbati wrote:
> I believe the paper is defective because while it explicitly says that
> nullptr_t matches both T* and T::*, but doesn't specify for which T!

I believe it cannot specify which T, because it specifies that T is
both T1 and T2. Both types have the opposite characteristics.

> Consider this:
>
>   template<typename T> struct X
>   {
>     X() { cout << "main " << typeid(T).name() << "\n";
>   };
>
>   template<typename T> struct X<T*>
>   {
>     X() { cout << "specialization " << typeid(T).name() << "\n";
>   };
>
>   X<nullptr_t> x;
>
> what should be the expected output of this code? The compiler must
> instantiate the template, either from the main definition of from the
> specialization. But if nullptr_t matches the specialization, the
> compiler *must* deduce T. Is it void? Is it some other type? Any choice
> is arbitrary.

In the the specialization for <T*>, the type T is T1, and the pointer
to T1 should be convertible to the pointer to any other type. We've
recently discussed what the type could it be:

http://groups.google.com/group/comp.std.c++/msg/0b607e99c3481f44
http://groups.google.com/group/comp.std.c++/msg/fa3b0573bade9026
http://groups.google.com/group/comp.std.c++/msg/ccf51aad08b8ae1b

Type T2 is the opposite: a pointer to any other type should be
convertible to the pointer to T2. Type T2 is almost like "void",
without the following limitations:

    - inability to inherit from void
    - inability to declare pointers to members of void
    - inability to declare reference to void


> Is T nullptr_t itself? It can't be, because nullptr_t* is
> a valid type.

It's valid in N1601, but there was no reason to allow it.


> IMHO, the only reasonable solution is that nullptr_t should *not* match
> the specialization but the main definition must be chosen with T =
> nullptr_t.

The questions:

    1. Is it pointer? If it is pointer,
       why doesn't it match the specialization for <T*>?

    2. Is it useful in generic programming?

The problem with shared_ptr is the problem that will arise each time
when we will have a template function with parameter of pointer or
pointer-to-member type:

    void f(char*);
    template<class T> void g(T* p) { f(p); }

    g(NULL);    // allowed
    g(nullptr); // disallowed?

Do you suggest to add an overload for nullptr_t, or is it what we
really want to avoid with nullptr?

And what if I have several parameters of pointer or pointer-to-member
type:

    template <class T1, class T2>
    int f(T1* p, T2::* pm) {
        if (p && pm) {
            return p->*pm;
        }
        return 0;
    }

    f(p, pm);               // ok
    f(p, nullptr);          // error
    f(nullptr, pm);         // error
    f(nullptr, nullptr);    // error

Should I provide another 3 overloads for nullptr_t?


--
Andrei Polushin

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: "Greg Herlihy" <greghe@pacbell.net>
Date: Wed, 15 Nov 2006 00:36:04 CST
Raw View
Alberto Ganesh Barbati wrote:
> Consider this:
>
>   template<typename T> struct X
>   {
>     X() { cout << "main " << typeid(T).name() << "\n";
>   };
>
>   template<typename T> struct X<T*>
>   {
>     X() { cout << "specialization " << typeid(T).name() << "\n";
>   };
>
>   X<nullptr_t> x;
>
> what should be the expected output of this code?

This program prints out an implementation-defined name for an
implementation-defined type - so its expected output would vary across
implementations.

> The compiler must
> instantiate the template, either from the main definition of from the
> specialization. But if nullptr_t matches the specialization, the
> compiler *must* deduce T. Is it void? Is it some other type? Any choice
> is arbitrary. Is T nullptr_t itself? It can't be, because nullptr_t* is
> a valid type.

Since nullptr_t is an implementation-defined typedef, the T deduced for
the pointer specialization of X, would be an implementation-defined
type.

> IMHO, the only reasonable solution is that nullptr_t should *not* match
> the specialization but the main definition must be chosen with T =
> nullptr_t.

Since nullptr_t is a pointer type, it must match the T* specialization.
There is no other, legal alternative.

> In the specific case of shared_ptr, you have to consider that the
> constructor:
>
>    template<class Y> shared_ptr(Y *p);
>
> is equivalent to use a deleter object that eventually calls
>
>    delete static_cast<Y*>(p);
>
> and not
>
>    delete static_cast<T*>(p);
>
> so the choice of Y is not irrelevant.

Nor is it important - which is the reason why the choice for Y's type
is not specified, but instead left as an implementation-level detail.

Greg

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: AlbertoBarbati@libero.it (Alberto Ganesh Barbati)
Date: Wed, 15 Nov 2006 15:54:47 GMT
Raw View
Greg Herlihy ha scritto:
> Alberto Ganesh Barbati wrote:
>> Consider this:
>>
>>   template<typename T> struct X
>>   {
>>     X() { cout << "main " << typeid(T).name() << "\n";
>>   };
>>
>>   template<typename T> struct X<T*>
>>   {
>>     X() { cout << "specialization " << typeid(T).name() << "\n";
>>   };
>>
>>   X<nullptr_t> x;
>>
>> what should be the expected output of this code?
>
> This program prints out an implementation-defined name for an
> implementation-defined type - so its expected output would vary across
> implementations.

That's reasonable. However, it's not specified in the paper. I know the
paper is two years old, so there surely was a lot of discussion about it
that I'm not aware of. (Ok, I could google for that, but maybe time has
come for a new paper that integrates those discussions.)

>> IMHO, the only reasonable solution is that nullptr_t should *not* match
>> the specialization but the main definition must be chosen with T =
>> nullptr_t.
>
> Since nullptr_t is a pointer type, it must match the T* specialization.
> There is no other, legal alternative.

There is no alternative according to the new knowledge you have introduced.

>> In the specific case of shared_ptr, you have to consider that the
>> constructor:
>>
>>    template<class Y> shared_ptr(Y *p);
>>
>> is equivalent to use a deleter object that eventually calls
>>
>>    delete static_cast<Y*>(p);
>>
>> and not
>>
>>    delete static_cast<T*>(p);
>>
>> so the choice of Y is not irrelevant.
>
> Nor is it important - which is the reason why the choice for Y's type
> is not specified, but instead left as an implementation-level detail.

Well, it is. shared_ptr requires that Y must be a complete type. That
would mean that the implementation-defined type should be complete as
well, or we would incur in undefined behavior.

Ganesh

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: "Greg Herlihy" <greghe@pacbell.net>
Date: Wed, 15 Nov 2006 23:57:35 CST
Raw View
Alberto Ganesh Barbati wrote:
> Greg Herlihy ha scritto:
> > Alberto Ganesh Barbati wrote:
> >> Consider this:
> >>
> >>   template<typename T> struct X
> >>   {
> >>     X() { cout << "main " << typeid(T).name() << "\n";
> >>   };
> >>
> >>   template<typename T> struct X<T*>
> >>   {
> >>     X() { cout << "specialization " << typeid(T).name() << "\n";
> >>   };
> >>
> >>   X<nullptr_t> x;
> >>
> >> what should be the expected output of this code?
> >
> > This program prints out an implementation-defined name for an
> > implementation-defined type - so its expected output would vary across
> > implementations.
>
> That's reasonable. However, it's not specified in the paper. I know the
> paper is two years old, so there surely was a lot of discussion about it
> that I'm not aware of. (Ok, I could google for that, but maybe time has
> come for a new paper that integrates those discussions.)

The typedef is specified in the paper. Specifically, the nullptr_t
typedef is described (as an addition to    8.1) on page 9 of the nullptr
proposal (N1601). I have no other information about nullptr apart from
this document.

> >> IMHO, the only reasonable solution is that nullptr_t should *not* match
> >> the specialization but the main definition must be chosen with T =
> >> nullptr_t.
> >
> > Since nullptr_t is a pointer type, it must match the T* specialization.
> > There is no other, legal alternative.
>
> There is no alternative according to the new knowledge you have introduced.

The "new knowledge" for which I should be credited - is the revelation
that the final page of a proposal may contain information other than
the biliography, notes or acknowledgments of the proposal. :-)

> >> In the specific case of shared_ptr, you have to consider that the
> >> constructor:
> >>
> >>    template<class Y> shared_ptr(Y *p);
> >>
> >> is equivalent to use a deleter object that eventually calls
> >>
> >>    delete static_cast<Y*>(p);
> >>
> >> and not
> >>
> >>    delete static_cast<T*>(p);
> >>
> >> so the choice of Y is not irrelevant.
> >
> > Nor is it important - which is the reason why the choice for Y's type
> > is not specified, but instead left as an implementation-level detail.
>
> Well, it is. shared_ptr requires that Y must be a complete type. That
> would mean that the implementation-defined type should be complete as
> well, or we would incur in undefined behavior.

More precisely, passing an incomplete Y type has an undefined effect
upon the Library. The Library, after all, must abide by the same C++
language rules as any other source code - and has no license to specify
additional, undefined behavior not already undefined by the rules of
the language.

In this case it would seem to make the most sense for shared_ptr to
have a (non-template), delegating constructor (taking a nullptr_t
parameter) that simply default-constructs an empty shared_ptr object.

Greg


---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: AlbertoBarbati@libero.it (Alberto Ganesh Barbati)
Date: Thu, 16 Nov 2006 19:33:46 GMT
Raw View
Greg Herlihy ha scritto:
> Alberto Ganesh Barbati wrote:
>> Greg Herlihy ha scritto:
>>> Alberto Ganesh Barbati wrote:
>>>> Consider this:
>>>>
>>>>   template<typename T> struct X
>>>>   {
>>>>     X() { cout << "main " << typeid(T).name() << "\n";
>>>>   };
>>>>
>>>>   template<typename T> struct X<T*>
>>>>   {
>>>>     X() { cout << "specialization " << typeid(T).name() << "\n";
>>>>   };
>>>>
>>>>   X<nullptr_t> x;
>>>>
>>>> what should be the expected output of this code?
>>> This program prints out an implementation-defined name for an
>>> implementation-defined type - so its expected output would vary acros=
s
>>> implementations.
>> That's reasonable. However, it's not specified in the paper. I know th=
e
>> paper is two years old, so there surely was a lot of discussion about =
it
>> that I'm not aware of. (Ok, I could google for that, but maybe time ha=
s
>> come for a new paper that integrates those discussions.)
>=20
> The typedef is specified in the paper. Specifically, the nullptr_t
> typedef is described (as an addition to =A78.1) on page 9 of the nullpt=
r
> proposal (N1601). I have no other information about nullptr apart from
> this document.

I've seen page 9. That typedef is only a partial answer to my request.
It says that nullptr_t is an implementation defined type, but it doesn't
say anything about T when nullptr_t matches T* as a template parameter.
Same thing for member pointers because matching a member pointer
involves more than one type (an additional type for a pointer to data
members and one or more additional types for a pointer to member function=
).

I seems that my answer to Andrei's post has been lost... I'll repost it,
as it contained some more thoughts of mine about this issues.

Ganesh

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]