Context: In an attempt to implement the copy and swap idiom on a templated homemade shared_ptr. I'm having trouble to get this function called:
template<typename U>
shared_ptr<T>& operator=(shared_ptr<U> rhs)
{
static_assert(std::is_base_of<T, U>::value);
swap(*this, rhs);
return *this;
}
Code stripped from the unecessary:
The following code does not compile. The "x=y" line is an attempt to use:
foo<T>& operator=(const foo<U>& ptr) = delete;
If I remove/comment the deleted functions line. Meaning if I do not define them, the compiler will do it for me and the code will compile. However,the special member generated by the compiler will be preferred to my operator which will not be called.
template<typename T>
class foo
{
public:
// copy&swap
template<typename U>
foo<T>& operator=(foo<U> rhs)
{
// whatever is needed swap and all
return *this;
}
template<typename U>
foo<T>& operator=(const foo<U>& ptr) = delete;
template<typename U>
foo<T>& operator=(foo<U>&& ptr) = delete;
foo<T>& operator=(foo<T>&& ptr) = delete;
};
int main()
{
foo<int> x,y;
x = y;
return 0;
}
Questions:
- The overload resolution is not clear to me here. Why is one signature preferred to the other?
- If the function is deleted, I understand it is not a "substitution failure", but still it is a failure at signature level, not inside the body, why does the compiler stop looking for a match?
- Any solution to get the function to be called ?
Edit/Answer
This is the correct version so far:
- Non-template constructors (move/copy) are marked explicit so that assigning foo into foo calls the template version.
- Appropriate static assert are added so conversion U/T makes sense
- Arguments are perfect forwarded to the assign/move internal functions
code:
#include <iostream>
template<typename T>
class foo
{
private:
template <typename U> friend class foo;
template<typename U>
void assign(const foo<U>& other)
{
static_assert(std::is_base_of<T, U>::value || std::is_convertible<U, T>::value);
std::cout << "templated assign function" << std::endl;
this->data = other.data;
}
template<typename U>
void move(foo<U>&& other)
{
static_assert(std::is_base_of<T, U>::value || std::is_convertible<U, T>::value);
std::cout << "templated move function" << std::endl;
this->swap(*this, other);
}
public:
template<class X, class Y> void swap(foo<X>& left, foo<Y>& right) noexcept
{
std::cout << "templated swap function" << std::endl;
std::swap(left.data, right.data);
}
void swap(foo<T>& left, foo<T>& right) noexcept
{
std::cout << "swap function" << std::endl;
std::swap(left.data, right.data);
}
foo() {}
explicit foo(foo<T>&& other)
{
std::cout << "move constructor foo(foo&& other)" << std::endl;
move(std::forward<decltype(other)>(other));
}
explicit foo(const foo<T>& other)
{
std::cout << "copy constructor foo(const foo& other)" << std::endl;
assign(std::forward<decltype(other)>(other));
}
template<typename U>
foo(foo<U>&& other)
{
static_assert(std::is_base_of<T, U>::value || std::is_convertible<U, T>::value);
std::cout << "templated move constructor template<typename U> foo(foo<U>&& other)" << std::endl;
move(std::forward<decltype(other)>(other));
}
template<typename U>
foo(const foo<U>& other)
{
static_assert(std::is_base_of<T, U>::value || std::is_convertible<U,T>::value);
std::cout << "templated copy constructor template<typename U> foo(const foo<U>& other)" << std::endl;
assign(std::forward<decltype(other)>(other));
}
// copy&swap
template<typename U>
foo<T>& operator=(foo<U> rhs)
{
static_assert(std::is_base_of<T, U>::value || std::is_convertible<U, T>::value);
std::cout << "templated assignement template<typename U> foo<T>& operator=(foo<U> rhs)" << std::endl;
auto tmp = rhs.data;
rhs.data = reinterpret_cast<U*>(data);
data = reinterpret_cast<T*>(tmp);
return *this;
}
foo<T>& operator=(foo<T> rhs)
{
std::cout << "assignement foo<T>& operator=(foo<T> rhs)" << std::endl;
std::swap(rhs.data,data);
return *this;
}
private:
T * data;
};
int main()
{
foo<int> x,y;
const foo<int>& cy = y;
foo<short> z, w;
x = y;
x = cy;
x = std::move(y);
x = z;
x = std::move(w);
return 0;
}