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.
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).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With