Search code examples
c++templatesambiguityimplicit-conversionambiguous-call

C++ templates and ambiguity problem


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)


Solution

  • 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;
    };