Search code examples
c++implicit-conversion

C++ Implicit cast for operator==


I'm having trouble understanding why one template instantiation works in my code and another does not.

I am using this class template I created:

template <typename T>
struct Allocated {
public:
    const T& get() const& {
        return *ptr.get();
    }
    T& get() & {
        return *ptr.get();
    }
    T&& get() && {
        return std::move(*ptr.get());
    }

    operator T& () & {
        return *ptr.get();
    }

    operator const T& () const & {
        return *ptr.get();
    }

    operator const T () && {
        return std::move(* ptr.get());
    }

    template <typename U>
    Allocated(const U& u) : ptr(std::make_unique<T>(u)) {}
    template <typename U>
    Allocated(U&& u) : ptr(std::make_unique<T>(std::move(u))) {}
    Allocated()  = delete;
    Allocated(const Allocated& other) : Allocated(other.get()) {};
    Allocated& operator=(const Allocated& other) {
        ptr = std::make_unique<T>(*other.ptr);
    }
    Allocated(Allocated&& other) : Allocated(other.get()) {};
    Allocated& operator=(Allocated&& other) {
        ptr = std::move(other.ptr);
        return *this;
    }
    ~Allocated() = default;
protected:
    std::unique_ptr<T> ptr;
};

and this code works

    Allocated<int> u = 7;
    bool k1 = u.get() == u.get();
    bool k2 = u == u; // this works fine and calls `operator T&`

while this does not

    Allocated<std::string> u = "test";
    bool k1 = u.get() == u.get();
    bool k2 = u == u; // error here

Question1: why is operator T& called for int and not for std::string?

Question 2: how can I fix it for any type? I do not want to define operator== in Allocated and it should work for other operators like Allocated<int>.operator>(Allocated<int>) for example.


Solution

  • The issue with this code is that it relies on operator == to provide context for implicit type conversion. It works for int because there is a built-in bool operator ==(int, int) candidate. When compiler checks this candidate function it knows types of arguments and is able to perform an implicit type conversion by invoking Allocated::operator int const &. However it does not work with string because corresponding operator for string is actually a template (because std::string is an alias for std::basic_string template) and therefore when compiler checks this candidate it does not know types of arguments and needs to deduce them first.

    The following simplified examples illustrate the difference:

    template <typename T>
    struct Allocated
    {
        operator T const & () const;
    };
    

    Type of argument of operator == is known to be NotTemplate const &, leading to successful implicit conversion. online compiler

    struct NotTemplate{};
    
    bool operator ==(NotTemplate const &, NotTemplate const &);
    
    Allocated<NotTemplate> not_template{};
    bool not_template_cmp{not_template == not_template}; // ok
    

    Type of argument of operator == is not known in advance, first Dummy must be deduced from Allocated<Template<void>>, which is impossible. online compiler

    template<typename Dummy>
    struct Template{};
    
    template<typename Dummy>
    bool operator ==(Template<Dummy> const &, Template<Dummy> const &);
    
    Allocated<Template<void>> _template{};
    bool _template_cmp{_template == _template}; // error, no match...
    

    Type of argument of an additional operator == overload is known to be Template<void>, leading to successful implicit conversion. online compiler

    template<typename Dummy>
    struct Template{};
    
    template<typename Dummy>
    bool operator ==(Template<Dummy> const &, Template<Dummy> const &);
    
    bool operator ==(Template<void> const &, Template<void> const &);
    
    Allocated<Template<void>> _template{};
    bool _template_cmp{_template == _template}; // ok
    

    One way to deal with this issue is to implement operator == (or maybe even a spaceship operator in fresh C++ standard) overloads, including those accepting T const & is one of tne arguments, for Allocated class (and btw it should probably check that ptr is not null).