I'm using the following example from Stroustrup C++ 4th Ed Page 764-765. I'm a bit confused on it's behavior and looking for guidance.
First, why doesn't Ptr<X> xp2 = py; compile, when there is a user defined conversion template function? FYI, I added this to the example.
Second, why does Ptr<X> xp{py}; invoke the user defined conversion operator, then when I step through in gdb, the copy constructor Ptr(const Ptr& r) is not invoked? Maybe something is being invoked implicitly?
template<typename T> class Ptr
{
T* p;
public:
Ptr(T* t) : p{t} {}
Ptr(const Ptr& r) {p = r.p;}
template<typename T2>
explicit operator Ptr<T2>();
};
template<class T>
template<class T2>
Ptr<T>::operator Ptr<T2>()
{
return Ptr<T2>{p};
}
class X {};
class Y : public X {};
int main(int argc, char *argv[])
{
Y y;
Ptr<Y> py{&y};
Ptr<X> xp{py};
//Ptr<X> xp2 = py; // no candidate, thought this would work
Ptr<X> x2{xp};
return 0;
}
First, why doesn't Ptr<X> xp2 = py; compile, when there is a user defined conversion template function? FYI, I added this to the example.
This requests implicit conversion to Ptr<X> but there is no implicit conversion operator. The conversion operator is declared explicit so it is not a candidate.
Second, why does Ptr<X> xp{py}; invoke the user defined conversion operator, then when I step through in gdb, the copy constructor Ptr(const Ptr& r) is not invoked?
The compiler is almost certainly eliding the copy. Try compiling with -fno-elide-constructors and then see if the copy constructor is invoked.
Note that copy/move elision is codified in C++17 by temporary materialization rules. In this case there isn't a copy/move at all (no such constructor is even required to exist) so if you are compiling in C++17 mode then even -fno-elide-constructors will not cause the copy constructor to be invoked.
Related
Consider the following program:
struct A {
A(int){}
A(A const &){}
};
int main() {
A y(5);
}
The variable y is direct-initialized with the expression 5.The overload resolution selects the constructor A::A(int), which is what I expect and want, but why does it happen?
It may be so for two reasons:
either the overload A::A(int) is a better match then A::A(A const &), or the second one is not a viable overload at all.
Question: In the above program, is the constructor A::A(A const &) a viable overload for initialization of y?
Yes, the rules for constructor overloading are same as for ordinary functions. The compiler is allowed to make one user-defined conversion per parameter - as pointed by Ben Voigt - in order to match the parameters with arguments. In this case it can do int->A through A(5)
This situation is the same as:
void foo(const std::string&);
void bar(const std::string&);//1
void bar(const char*);//2
//...
foo("Hello");//Is allowed
bar("Hello");//Calls 2 as it matches exactly without a need for conversion.
So, the answer, yes it's viable overload but it's not chosen because according to overloading rules the A(int) constructor is a better match.
[class.conv.ctor]/1:
A constructor that is not explicit ([dcl.fct.spec]) specifies a
conversion from the types of its parameters (if any) to the type of
its class. Such a constructor is called a converting constructor.
[ Example:
struct X {
X(int);
X(const char*, int =0);
X(int, int);
};
void f(X arg)
{
X a = 1; // a = X(1)
X b = "Jessie"; // b = X("Jessie",0)
a = 2; // a = X(2)
f(3); // f(X(3))
f({1, 2}); // f(X(1,2))
}
— end example ]
and
[over.match.copy]/1:
Assuming that “cv1 T” is the type of the object being initialized, with T a class type, the candidate functions are selected as follows:
(1.1) The converting constructors of T are candidate functions.
This page says that the make_optional function in C++17 returns a constexpr optional<...>. I think (I might be wrong though) this would require that optional<T> has a constexpr copy or move constructor. However, this page also says that's not the case.
I don't know how make_optional can be implemented as the C++1z draft currently stands. See this post for clarification. Is there some workaround, or maybe it's just the standard draft/cppreference's mistake?
Thanks to #Yakk and #T.C. for their explanations. I feel an example should make things clearer:
struct wrapper {
int value;
// non-explicit constexpr constructor
constexpr wrapper(int v) noexcept : value(v) {}
// non-constexpr copy & move constructors
wrapper(const wrapper& that) noexcept : value(that.value) {}
wrapper(wrapper&& that) noexcept : value(that.value) {}
};
constexpr wrapper make_wrapper(int v)
{
return {v};
}
int main()
{
constexpr auto x = make_wrapper(123); // error! copy/move construction,
// but no constexpr copy/move ctor
constexpr int y = make_wrapper(123).value; // ok
static_assert(y == 123, ""); // passed
}
So make_wrapper does successfully return a constexpr wrapper; it's the copy/move construction (although usually elided by compilers) that prevents the code from compiling, since there is no constexpr copy/move constructor.
We can verify the constexpr-ness of the returned (temporary) wrapper object by using its member value to initialize a constexpr variable.
You can directly construct return values in C++11 with return {something}; If there are any non-explicit ctors that are constexpr you can return it from a function.
Consider the following self contained Code.
#include <iostream>
template<typename Ty>
class Foo {
private:
Ty m_data;
public:
Foo() :m_data() {}
Foo(Ty data) :m_data(data) {}
template<typename U>
Foo& operator=(Foo<U> rv)
{
m_data = rv.m_data;
return *this;
}
private:
Foo(Foo&);
Foo& operator=(Foo&);
};
int main()
{
Foo<int> na(10);
Foo<int> nb;
nb = Foo<int>(10); // (1)
Foo<int>(10); // (2)
}
My understanding is statement (1) is an assignment rather than a Copy COnstructor. Yet, when compiling (VC++ and G++), the Error Message states, it tries to match a Copy Constructor which was declared private.
1>Source.cpp(23): error C2248: 'Foo<int>::Foo' : cannot access private member declared in class 'Foo<int>'
1> Source.cpp(16) : see declaration of 'Foo<int>::Foo'
My question is, why does it try to search for a Copy Constructor instead of an assignment.
Note, I know it is the assignment that is failing because (2) compiles fine without any error.
Your assignment operator takes its parameter by value, which requires making a copy. That copy may (or may not) be elided - but the copy constructor still needs to be available and accessible, even if not called.
There are two issues:
Your private assignment operator Foo& operator=(Foo&); takes a non-const lvalue reference. That means it cannot be selected as an overload in nb = Foo<int>(10);, because the RHS is an rvalue
That leads to your template assignment operator being selected. But that takes its argument by value, requiring a copy or move copy constructor.
If you fix 1. to take a const reference, gcc gives the following error:
error: 'Foo& Foo::operator=(const Foo&) [with Ty = int]' is private
If you fix 2. so that the template assignment operator takes a const reference, the code compiles without errors.
Your assignment operator passes argument by value, so it uses copy ctor:
template<typename U>
Foo& operator=(Foo<U> rv)
Possible solution to pass it by const refernce:
template<typename U>
Foo& operator=(const Foo<U> &rv)
A private version
private:
Foo(Foo&);
Foo& operator=(Foo&);
cannot be called because it takes non-const lvalue reference so
Foo& operator=(Foo<U> rv)
this version is called but it takes parameter by value and copy constructor has to be invoked.
I recently started learning about type erasures. It turned out that this technique can greatly simplify my life. Thus I tried to implement this pattern. However, I experience some problems with the copy- and move-constructor of the type erasure class.
Now, lets first have a look on the code, which is quite straight forward
#include<iostream>
class A //first class
{
private:
double _value;
public:
//default constructor
A():_value(0) {}
//constructor
A(double v):_value(v) {}
//copy constructor
A(const A &o):_value(o._value) {}
//move constructor
A(A &&o):_value(o._value) { o._value = 0; }
double value() const { return _value; }
};
class B //second class
{
private:
int _value;
public:
//default constructor
B():_value(0) {}
//constructor
B(int v):_value(v) {}
//copy constructor
B(const B &o):_value(o._value) {}
//move constructor
B(B &&o):_value(o._value) { o._value = 0; }
//some public member
int value() const { return _value; }
};
class Erasure //the type erasure
{
private:
class Interface //interface of the holder
{
public:
virtual double value() const = 0;
};
//holder template - implementing the interface
template<typename T> class Holder:public Interface
{
public:
T _object;
public:
//construct by copying o
Holder(const T &o):_object(o) {}
//construct by moving o
Holder(T &&o):_object(std::move(o)) {}
//copy constructor
Holder(const Holder<T> &o):_object(o._object) {}
//move constructor
Holder(Holder<T> &&o):_object(std::move(o._object)) {}
//implements the virtual member function
virtual double value() const
{
return double(_object.value());
}
};
Interface *_ptr; //pointer to holder
public:
//construction by copying o
template<typename T> Erasure(const T &o):
_ptr(new Holder<T>(o))
{}
//construction by moving o
template<typename T> Erasure(T &&o):
_ptr(new Holder<T>(std::move(o)))
{}
//delegate
double value() const { return _ptr->value(); }
};
int main(int argc,char **argv)
{
A a(100.2344);
B b(-100);
Erasure g1(std::move(a));
Erasure g2(b);
return 0;
}
As a compiler I use gcc 4.7 on a Debian testing system. Assuming the code is stored in a file named terasure.cpp the build leads to the following error message
$> g++ -std=c++0x -o terasure terasure.cpp
terasure.cpp: In instantiation of ‘class Erasure::Holder<B&>’:
terasure.cpp:78:45: required from ‘Erasure::Erasure(T&&) [with T = B&]’
terasure.cpp:92:17: required from here
terasure.cpp:56:17: error: ‘Erasure::Holder<T>::Holder(T&&) [with T = B&]’ cannot be overloaded
terasure.cpp:54:17: error: with ‘Erasure::Holder<T>::Holder(const T&) [with T = B&]’
terasure.cpp: In instantiation of ‘Erasure::Erasure(T&&) [with T = B&]’:
terasure.cpp:92:17: required from here
terasure.cpp:78:45: error: no matching function for call to ‘Erasure::Holder<B&>::Holder(std::remove_reference<B&>::type)’
terasure.cpp:78:45: note: candidates are:
terasure.cpp:60:17: note: Erasure::Holder<T>::Holder(Erasure::Holder<T>&&) [with T = B&]
terasure.cpp:60:17: note: no known conversion for argument 1 from ‘std::remove_reference<B&>::type {aka B}’ to ‘Erasure::Holder<B&>&&’
terasure.cpp:58:17: note: Erasure::Holder<T>::Holder(const Erasure::Holder<T>&) [with T = B&]
terasure.cpp:58:17: note: no known conversion for argument 1 from ‘std::remove_reference<B&>::type {aka B}’ to ‘const Erasure::Holder<B&>&’
terasure.cpp:54:17: note: Erasure::Holder<T>::Holder(const T&) [with T = B&]
terasure.cpp:54:17: note: no known conversion for argument 1 from ‘std::remove_reference<B&>::type {aka B}’ to ‘B&’
It seems that for Erasure g2(b); the compiler still tries to use the move constructor. Is this intended behavior of the compiler? Do I missunderstand something in general with the type erasure pattern? Does someone have an idea how to get this right?
As evident from the compiler errors, the compiler is trying to instantiate your Holder class for T = B&. This means that the class would store a member of a reference type, which gives you some problems on copy and such.
The problem lies in the fact that T&& (for deduced template arguments) is an universal reference, meaning it will bind to everything. For r-values of B it will deduce T to be B and bind as an r-value reference, for l-values it will deduce T to be B& and use reference collapsing to interpret B& && as B& (for const B l-values it would deduce T to be const B& and do the collapsing). In your example b is a modifiable l-value, making the constructor taking T&& (deduced to be B&) a better match then the const T& (deduced to be const B&) one. This also means that the Erasure constructor taking const T& isn't really necessary (unlike the one for Holder due to T not being deduced for that constructor).
The solution to this is to strip the reference (and probably constness, unless you want a const member) from the type when creating your holder class. You should also use std::forward<T> instead of std::move, since as mentioned the constructor also binds to l-values and moving from those is probably a bad idea.
template<typename T> Erasure(T&& o):
_ptr(new Holder<typename std::remove_cv<typename std::remove_reference<T>::type>::type>(std::forward<T>(o))
{}
There is another bug in your Erasure class, which won't be caught by the compiler: You store your Holder in a raw pointer to heap allocated memory, but have neither custom destructor to delete it nor custom handling for copying/moving/assignment (Rule of Three/Five). One option to solve that would be to implement those operations (or forbid the nonessential ones using =delete). However this is somewhat tedious, so my personal suggestion would be not to manage memory manually, but to use a std::unique_ptr for memory management (won't give you copying ability, but if you want that you first need to expand you Holder class for cloning anyways).
Other points to consider:
Why are you implementing custom copy/move constructors for Erasure::Holder<T>, A and B? The default ones should be perfectly fine and won't disable the generation of a move assignment operator.
Another point is that Erasure(T &&o) is problematic in that it will compete with the copy/move constructor (T&& can bind to Èrasure& which is a better match then both const Erasure& and Erasure&&). To avoid this you can use enable_if to check against types of Erasure, giving you something similar to this:
template<typename T, typename Dummy = typename std::enable_if<!std::is_same<Erasure, std::remove_reference<T>>::value>::type>
Erasure(T&& o):
_ptr(new Holder<typename std::remove_cv<typename std::remove_reference<T>::type>::type>(std::forward<T>(o))
{}
Your problem is that the type T is deduced to be a reference by your constructor taking a universal reference. You want to use something along the lines of this:
#include <type_traits>
class Erasure {
....
//construction by moving o
template<typename T>
Erasure(T &&o):
_ptr(new Holder<typename std::remove_reference<T>::type>(std::forward<T>(o)))
{
}
};
That is, you need to remove any references deduced from T (and probably also any cv qualifier but the correction doesn't do that). and then you don't want to std::move() the argument o but std::forward<T>() it: using std::move(o) could have catastrophic consequences in case you actually do pass a non-const reference to a constructor of Erasure.
I didn't pay too much attention to the other code put as far as I can tell there also a few semantic errors (e.g., you either need some form of reference counting or a form of clone()int the contained pointers, as well as resource control (i.e., copy constructor, copy assignment, and destructor) in Erasure.
EDIT: solved see comments
--don't know how to mark as solved with out an answer.
After watching a Channel 9 video on Perfect Forwarding / Move semantics in c++0x i was some what led into believing this was a good way to write the new assignment operators.
#include <string>
#include <vector>
#include <iostream>
struct my_type
{
my_type(std::string name_)
: name(name_)
{}
my_type(const my_type&)=default;
my_type(my_type&& other)
{
this->swap(other);
}
my_type &operator=(my_type other)
{
swap(other);
return *this;
}
void swap(my_type &other)
{
name.swap(other.name);
}
private:
std::string name;
void operator=(const my_type&)=delete;
void operator=(my_type&&)=delete;
};
int main()
{
my_type t("hello world");
my_type t1("foo bar");
t=t1;
t=std::move(t1);
}
This should allow both r-values and const& s to assigned to it. By constructing a new object with the appropriate constructor and then swapping the contents with *this. This seems sound to me as no data is copied more than it need to be. And pointer arithmetic is cheap.
However my compiler disagrees. (g++ 4.6) And I get these error.
copyconsttest.cpp: In function ‘int main()’:
copyconsttest.cpp:40:4: error: ambiguous overload for ‘operator=’ in ‘t = t1’
copyconsttest.cpp:40:4: note: candidates are:
copyconsttest.cpp:18:11: note: my_type& my_type::operator=(my_type)
copyconsttest.cpp:30:11: note: my_type& my_type::operator=(const my_type&) <deleted>
copyconsttest.cpp:31:11: note: my_type& my_type::operator=(my_type&&) <near match>
copyconsttest.cpp:31:11: note: no known conversion for argument 1 from ‘my_type’ to ‘my_type&&’
copyconsttest.cpp:41:16: error: ambiguous overload for ‘operator=’ in ‘t = std::move [with _Tp = my_type&, typename std::remove_reference< <template-parameter-1-1> >::type = my_type]((* & t1))’
copyconsttest.cpp:41:16: note: candidates are:
copyconsttest.cpp:18:11: note: my_type& my_type::operator=(my_type)
copyconsttest.cpp:30:11: note: my_type& my_type::operator=(const my_type&) <deleted>
copyconsttest.cpp:31:11: note: my_type& my_type::operator=(my_type&&) <deleted>
Am I doing something wrong? Is this bad practice (I don't think there is way of testing whether you are self assigning)? Is the compiler just not ready yet?
Thanks
Be very leery of the copy/swap assignment idiom. It can be sub-optimal, especially when applied without careful analysis. Even if you need strong exception safety for the assignment operator, that functionality can be otherwise obtained.
For your example I recommend:
struct my_type
{
my_type(std::string name_)
: name(std::move(name_))
{}
void swap(my_type &other)
{
name.swap(other.name);
}
private:
std::string name;
};
This will get you implicit copy and move semantics which forward to std::string's copy and move members. And the author of std::string knows best how to get those operations done.
If your compiler does not yet support implicit move generation, but does support defaulted special members, you can do this instead:
struct my_type
{
my_type(std::string name_)
: name(std::move(name_))
{}
my_type(const mytype&) = default;
my_type& operator=(const mytype&) = default;
my_type(mytype&&) = default;
my_type& operator=(mytype&&) = default;
void swap(my_type &other)
{
name.swap(other.name);
}
private:
std::string name;
};
You may also choose to do the above if you simply want to be explicit about your special members.
If you're dealing with a compiler that does not yet support defaulted special members (or implicit move members), then you can explicitly supply what the compiler should eventually default when it becomes fully C++11 conforming:
struct my_type
{
my_type(std::string name_)
: name(std::move(name_))
{}
my_type(const mytype& other)
: name(other.name) {}
my_type& operator=(const mytype& other)
{
name = other.name;
return *this;
}
my_type(mytype&& other)
: name(std::move(other.name)) {}
my_type& operator=(mytype&& other)
{
name = std::move(other.name);
return *this;
}
void swap(my_type &other)
{
name.swap(other.name);
}
private:
std::string name;
};
If you really need strong exception safety for assignment, design it once and be explicit about it (edit to include suggestion by Luc Danton):
template <class C>
typename std::enable_if
<
std::is_nothrow_move_assignable<C>::value,
C&
>::type
strong_assign(C& c, C other)
{
c = std::move(other);
return c;
}
template <class C>
typename std::enable_if
<
!std::is_nothrow_move_assignable<C>::value,
C&
>::type
strong_assign(C& c, C other)
{
using std::swap;
static_assert(std::is_nothrow_swappable_v<C>, // C++17 only
"Not safe if you move other into this function");
swap(c, other);
return c;
}
Now your clients can choose between efficiency (my type::operator=), or strong exception safety using strong_assign.
Did you closely read the error message? It sees two errors, that you have multiple copy-assignment operators and multiple move-assignment operators. And it's exactly right!
Special members must be specified at most once, no matter if they're defaulted, deleted, conventionally defined, or implicitly handled by being left out. You have two copy-assignment operators (one taking my_type, the other taking my_type const &) and two move-assignment operators (one taking my_type, the other taking my_type &&). Note that the assignment operator that takes my_type can handle lvalue and rvalue references, so it acts as both copy- and move-assignment.
The function signature of most special members have multiple forms. You must pick one; you cannot use an unusual one and then delete the conventional one, because that'll be a double declaration. The compiler will automatically use an unusually-formed special member and won't synthesize a special member with the conventional signature.
(Notice that the errors mention three candidates. For each assignment type, it sees the appropriate deleted method, the method that takes my_type, and then the other deleted method as an emergency near-match.)
Are you supposed to be deleting those overloads of the assignment operator? Shouldn't your declaration of the assignment operator be a template or something? I don't really see how that is supposed to work.
Note that even if that worked, by implementing the move assignment operator that way, the resources held by the object that was just moved from will be released at the time its lifetime ends, and not at the point of the assignment. See here for more details:
http://cpp-next.com/archive/2009/09/your-next-assignment/