overloaded constructor illegal with move and copy - c++

I have a class that I'm writing that will take a special type for one of its constructors that could be any type that fits my requirements. I have run into the issue that this templated constructor causes my copy and move constructors to be illegal overloads!
My class is layed out like this:
template<typename ... Types>
class myclass{
public:
myclass(const myclass &other){/* copy constructor */}
myclass(myclass &&other){/* move constructor */}
template<typename Special>
myclass(Special &&arg){/* stops copy/move implementations */}
}
How can I get around this limitation?

Constrain it.
template<typename Special,
std::enable_if_t<!std::is_same<std::decay_t<Special>, myclass>{}, int> = 0 >
myclass(Special &&arg) { /* ... */ }
Depending on your particular use case, you may also want to constrain Special further to only types that fit your requirements.

This example shows the different cases:
const myclass c1(42); // special: int
myclass c2(c1); // copy
myclass c3(c2); // special: myclass& (non const)
myclass c4(std::move(c3)); // move
Live Demo
your copy/move constructors are still legal overloads, but non const l-value has an exact match with your template constructor.
You may:
use SFINAE to forbid myclass& in the template (as in T.C's answer)
provide an other overload (with exact match):
myclass(myclass &other) : myclass(static_cast<const myclass &>(other)) {}
Demo

Related

How to copy a built-in array via copy-constructor?

We know that a built-in array can neither be copied nor be assigned. So If it is a member data of a class/struct/union It is OK to leave the compiler do its magic to copy them:
struct ArrInt5{
ArrInt5() = default;
ArrInt5(ArrInt5 const&) = default; // OK
int a[5];
};
ArrInt5 a, b = a; // OK
Sometimes that is not the case for example if the array holds objects of non-default- constructible objects. in that
case we do need to define our copy-ctor to do the job:
struct Bar{
// deleted default ctor
Bar(int x) : x_(x){}
int x_ = 0;
};
struct Foo{
Foo();
Foo(Foo const&);
Bar arr_[5];
};
Foo::Foo() : arr_{0, 1, 2, 3, 4}
{}
Foo::Foo(Foo const& rhs) : arr_{rhs.arr_[0], rhs.arr_[1], rhs.arr_[2], rhs.arr_[3], rhs.arr_[4]}
{}
As you can see Foo has a built-in array of five objects of type struct Bar that type is not default-constructible so the default ctor and the copy ctor must initialize it (arr_).
The problem is: How could initialize that array if it is of a big size lets say 1 million element? Should I hard copy them element by element? or there's some workaround?
I know so many will recommend me to use the equivalent STL container std::array but I'm not on that topic, I'm asking whether there's a workaround for my built-in array of non default-constructible objects.
The only way to generate this effect for you automatically is to wrap your array in a class type of some kind that has a compiler-generated copy constructor (e.g. such as a defaulted constructor). In your particular example, you could just have Foo(const Foo&) be defaulted:
struct Foo{
Foo();
Foo(Foo const&) = default;
// This generates your:
// "Foo::Foo(Foo const& rhs) : arr_{rhs.arr_[0], rhs.arr_[1], rhs.arr_[2], rhs.arr_[3], rhs.arr_[4]}"
// by the compiler
Bar arr_[5];
};
However the above only works if your copy-logic is trivial. For more complex copying, you can't always have a default copy constructor -- but we can solve this more generally.
If you wrap an array data member of any type/size in a struct or class and make sure it has compiler-generated copy constructors, then copying is easy in a constructor.
For example:
template <typename T, std::size_t N>
struct Array {
...
T data[N];
};
This allows for the simple case of copying:
Array<Bar,1000000> a = {{...}};
Array<Bar,1000000> b = a;
Or the more complex case of copying in a constructor that might require more logic than the compiler-generated ones:
class Foo {
...
Foo(const Foo& other);
...
Array<Bar,100000> arr_;
};
Foo::Foo(const Foo& other)
: arr_{other.arr_}
{
... other complex copy logic ...
}
At which point, congratulations -- you have invented std::array!
std::array was added to the standard library exactly because it was this simple to introduce a library-solution to this problem without having to alter the language itself. Don't shy away from it.

How to get if a type is truly move constructible

Take for example this code:
#include <type_traits>
#include <iostream>
struct Foo
{
Foo() = default;
Foo(Foo&&) = delete;
Foo(const Foo&) noexcept
{
std::cout << "copy!" << std::endl;
};
};
struct Bar : Foo {};
static_assert(!std::is_move_constructible_v<Foo>, "Foo shouldn't be move constructible");
// This would error if uncommented
//static_assert(!std::is_move_constructible_v<Bar>, "Bar shouldn't be move constructible");
int main()
{
Bar bar {};
Bar barTwo { std::move(bar) };
// prints "copy!"
}
Because Bar is derived from Foo, it doesn't have a move constructor. It is still constructible by using the copy constructor. I learned why it chooses the copy constructor from another answer:
if y is of type S, then std::move(y), of type S&&, is reference compatible with type S&. Thus S x(std::move(y)) is perfectly valid and call the copy constructor S::S(const S&).
—Lærne, Understanding std::is_move_constructible
So I understand why a rvalue "downgrades" from moving to a lvalue copying, and thus why std::is_move_constructible returns true. However, is there a way to detect if a type is truly move constructible excluding the copy constructor?
There are claims that presence of move constructor can't be detected and on surface they seem to be correct -- the way && binds to const& makes it impossible to tell which constructors are present in class' interface.
Then it occurred to me -- move semantic in C++ isn't a separate semantic... It is an "alias" to a copy semantic, another "interface" that class implementer can "intercept" and provide alternative implementation. So the question "can we detect a presence of move ctor?" can be reformulated as "can we detect a presence of two copy interfaces?". Turns out we can achieve that by (ab)using overloading -- it fails to compile when there are two equally viable ways to construct an object and this fact can be detected with SFINAE.
30 lines of code are worth a thousand words:
#include <type_traits>
#include <utility>
#include <cstdio>
using namespace std;
struct S
{
~S();
//S(S const&){}
//S(S const&) = delete;
//S(S&&) {}
//S(S&&) = delete;
};
template<class P>
struct M
{
operator P const&();
operator P&&();
};
constexpr bool has_cctor = is_copy_constructible_v<S>;
constexpr bool has_mctor = is_move_constructible_v<S> && !is_constructible_v<S, M<S>>;
int main()
{
printf("has_cctor = %d\n", has_cctor);
printf("has_mctor = %d\n", has_mctor);
}
Notes:
you probably should be able to confuse this logic with additional const/volatile overloads, so some additional work may be required here
doubt this magic works well with private/protected constructors -- another area to look at
doesn't seem to work on MSVC (as is tradition)
How to find out whether or not a type has a move constructor?
Assuming that the base class comes from the upstream, and the derived class is part of your application, there is no further decision you can make, once you decided to derive 'your' Bar from 'their' Foo.
It is the responsibility of the base class Foo to define its own constructors. That is an implementation detail of the base class. The same is true for the derived class. Constructors are not inherited. Trivially, both classes have full control over their own implementation.
So, if you want to have a move constructor in the derived class, just add one:
struct Bar : Foo {
Bar(Bar&&) noexcept {
std::cout << "move!" << std::endl;
};
};
If you don't want any, delete it:
struct Bar : Foo {
Bar(Bar&&) = delete;
};
If you do the latter, you can also uncomment the second static_assert without getting an error.

Is it possible to have a default copy constructor and a templated conversion constructor?

If I have a class like this
template <typename T>
class MyClass
{
T myData;
public:
T getValue() { return myData; }
template <typename V>
MyClass(const MyClass<V>& other) :
myData((T) other.getValue())
{
}
};
This would mean that I provide a copy constructor (for V=T) and thus according to this link Why no default move-assignment/move-constructor? I do not get default move constructors etc.
Is there a way to have the templated constructor only work as conversion constructors, so for V!=T?
Your premise is wrong. A constructor template is never used to instantiate a copy (or move) constructor. In other words, a copy/move constructor is always a non-template member function, even if a member function template could produce a constructor with the appropriate copy/move signature.
So as is, your class will still have a normal copy constructor in addition to the template.

template copy constructor

Given the following code, does Foo have copy constructor? Is it safe to use Foo with STL containers?
class Foo
{
public:
Foo() {}
template <typename T>
Foo(const T&) {}
};
The standard explicitly says that a copy constructor is a non-templated constructor that takes a reference to a possibly const-volatile object of the same type. In the code above you have a conversion but not copy constructor (i.e. it will be used for everything but copies, where the implicitly declared constructor will be used).
Does Foo have a copy constructor?
Yes, the implicitly declared/defined copy constructor.
Is it safe to use Foo with standard library containers?
With the current definition of Foo it is, but in the general case, it depends on what members Foo has and whether the implicitly defined copy constructor manages those correctly.
According to the Standard, a copy-constructor must be one of the following signature:
Foo(Foo &);
Foo(Foo const &);
Foo(Foo volatile &);
Foo(Foo const volatile &);
Foo(Foo&, int = 0, );
Foo(Foo&, int = 0, float = 1.0); //i.e the rest (after first) of the
//parameter(s) must have default values!
Since the template constructor in your code doesn't match with the form of any of the above, that is not copy-constructor.
Foo has a compiler generated copy constructor, which cannot be replaced by the template conversion constructor you have provided.
Foo f0;
Foo f1(f0); // calls compiler-synthesized copy constructor
Foo f2(42); // calls template conversion constructor with T=int

C++ covariant templates

I feel like this one has been asked before, but I'm unable to find it on SO, nor can I find anything useful on Google. Maybe "covariant" isn't the word I'm looking for, but this concept is very similar to covariant return types on functions, so I think it's probably correct. Here's what I want to do and it gives me a compiler error:
class Base;
class Derived : public Base;
SmartPtr<Derived> d = new Derived;
SmartPtr<Base> b = d; // compiler error
Assume those classes are fully fleshed out... I think you get the idea. It can't convert a SmartPtr<Derived> into a SmartPtr<Base> for some unclear reason. I recall that this is normal in C++ and many other languages, though at the moment I can't remember why.
My root question is: what is the best way to perform this assignment operation? Currently, I'm pulling the pointer out of the SmartPtr, explicitly upcasting it to the base type, then wrapping it in a new SmartPtr of the appropriate type (note that this is not leaking resources because our home-grown SmartPtr class uses intrusive reference counting). That's long and messy, especially when I then need to wrap the SmartPtr in yet another object... any shortcuts?
SmartPtr<Base> and SmartPtr<Derived> are two distinct instantiations of a the SmartPtr template. These new classes do not share the inheritance that Base and Derived do. Hence, your problem.
what is the best way to perform this assignment operation?
SmartPtr<Base> b = d;
Does not invoke assignment operator. This invokes the copy-ctor (the copy is elided in most cases) and is exactly as if you wrote:
SmartPtr<Base> b(d);
Provide for a copy-ctor that takes a SmartPtr<OtherType> and implement it. Same goes for the assignment operator. You will have to write out the copy-ctor and op= keeping in mind the semantics of SmartPtr.
Both the copy constructor and the assignment operator should be able to take a SmartPtr of a different type and attempt to copy the pointer from one to the other. If the types aren't compatible, the compiler will complain, and if they are compatible, you've solved your problem. Something like this:
template<class Type> class SmartPtr
{
....
template<class OtherType> SmartPtr(const SmartPtr<OtherType> &blah) // same logic as the SmartPtr<Type> copy constructor
template<class OtherType> SmartPtr<Type> &operator=(const SmartPtr<OtherType> &blah) // same logic as the SmartPtr<Type> assignment operator
};
Templates are not covariant, and that's good; imagine what would happen in the following case:
vector<Apple*> va;
va.push_back(new Apple);
// Now, if templates were covariants, a vector<Apple*> could be
// cast to a vector<Fruit*>
vector<Fruit*> & vf = va;
vf.push_back(new Orange); // Bam, we just added an Orange among the Apples!
To achieve what you are trying to do, the SmartPointer class must have a templatized constructor, that takes either another SmartPointer or a pointer of another type. You could have a look at boost::shared_ptr, which does exactly that.
template <typename T>
class SmartPointer {
T * ptr;
public:
SmartPointer(T * p) : ptr(p) {}
SmartPointer(const SmartPointer & sp) : ptr(sp.ptr) {}
template <typename U>
SmartPointer(U * p) : ptr(p) {}
template <typename U>
SmartPointer(const SmartPointer<U> & sp) : ptr(sp.ptr) {}
// Do the same for operator= (even though it's not used in your example)
};
Depends on the SmartPtr class. If it has a copy constructor (or in your case, assignment operator) that takes SmartPtr<T>, where T is the type it was constructed with, then it isn't going to work, because SmartPtr<T1> is unrelated to SmartPtr<T2> even if T1 and T2 are related by inheritance.
However, if SmartPtr has a templatized copy constructor/assignment operator, with template parameter TOther, that accepts SmartPtr<TOther>, then it should work.
Assuming you have control of the SmartPtr class, the solution is to provide a templated constructor:
template <class T>
class SmartPtr
{
T *ptr;
public:
// Note that this IS NOT a copy constructor, just another constructor that takes
// a similar looking class.
template <class O>
SmartPtr(const SmartPtr<O> &src)
{
ptr = src.GetPtr();
}
// And likewise with assignment operator.
};
If the T and O types are compatible, it will work, if they aren't you'll get a compile error.
I think the easiest thing is to provide automatic conversion to another SmartPtr according to the following:
template <class T>
class SmartPtr
{
public:
SmartPtr(T *ptr) { t = ptr; }
operator T * () const { return t; }
template <class Q> operator SmartPtr<Q> () const
{ return SmartPtr<Q>(static_cast<Q *>(static_cast<T *>(* this))); }
private:
T *t;
};
Note that this implementation is robust in the sense that the conversion operator template does not need to know about the semantics of the smart pointer, so reference counting does not need to replicated etc.