views:

253

answers:

2

I have a subset of a pointer class that look like:

template <typename T>
struct Pointer
{
     Pointer();
     Pointer(T *const x);
     Pointer(const Pointer &x);
     template <typename t>
     Pointer(const Pointer<t> &x);

     operator T *() const;
};

The goal of the last constructor is to allow to pass a Pointer of a subclass, or basically any type that is implicitly convertable to T *. This actual rule is only enforced by the definition of the constructor and the compiler can't actually figure it out by the declaration alone. If I drop it, and try to pass a Pointer<Sub> to a constructor of Pointer<Base>, I get a compile error, despite of the possible path through operator T *().

While it solves the above problem, it creates another one. If I have an overloaded function whose one overload takes a Pointer<UnrelatedClass> and the other takes Pointer<BaseClass>, and I try to invoke it with a Pointer<SubClass>, I get an ambiguity between the two overloads, with the intention, ofcourse, that the latter overload will be called.

Any suggestions? (Hopefully I was clear enough)

A: 

Try to make the constructor in question explicit, e.g.:

 template <typename t>
 explicit Pointer(const Pointer<t> &x);

And/or remove the operator T *() const; - I think this one will also create an ambiguity.

EDIT

Check the std::auto_ptr interface, and compare with yours. At least they solved the ambiguity.

frunsi
Adding `explicilt` to the declaration has the same effect a removing it altogether: it generates a compile error (none of the 2 overloads could convert all the argument types...). Removing the casting operator doesn't solve the amibguity (And is a valuable part of the class anyway).
cvb
You please add the code, where Pointer is used, and please add (as comment) where a compile error occures, and which error..
frunsi
Though the casting operator could still be the reason for the ambuigity (as Alexey already pointed out), its a bad idea to have implicit conversion: std::auto_ptr did not add it, and boost ptrs do not add it for the same reason (ambuigity in some cases).
frunsi
+1  A: 

The cure for your problem is called SFINAE (substitution failure is not an error)

#include "boost/type_traits/is_convertible.hpp"
#include "boost/utility/enable_if.hpp"

template<typename T>
class Pointer {
   ...
   template<typename U>
   Pointer(const Pointer<U> &x,
      typename boost::enable_if<
         boost::is_convertible<U*,T*>
      >::type* =0)
   : ...
   {
     ...
   }
   ...
};

If U* is convertible to T* the enable_if will have a typedef member type defaulting to void. Then, everything is fine. If U* is not convertible to T* this typedef member is missing, substitution fails and the constructor template is ignored.

This solves your conversion and ambiguity problems.

In response to the comment: is_convertible looks something like this:

typedef char one;         // sizeof == 1  per definition
struct two {char c[2];};  // sizeof != 1

template<typename T, typename U>
class is_convertible {
    static T source();
    static one sink(U);
    static two sink(...);
public:
    static const bool value = sizeof(sink(source()))==1;
};
sellibitze
Got the point, nice idea. The project doesn't use boost at the moment. Any idea how `is_convertible` works?
cvb
Yes. Feel free to upvote. :)
sellibitze
Would have upvoted ten times, but I'm not registered :)
cvb