C++0X explicitly defaulted copy constructors


M

Marc

See below for the code. A variadic argument U&&... matches a
copy-from-lvalue better than the implicit copy constructors, so I need
to declare a copy constructor from lvalue. And since its existence
prevents the implicit declaration of the other two versions (from const
ref and from rvalue), I need to declare those 2 as well. Now I don't
want anything fancy for those and I am fine with whatever the compiler
can generate by default, so I default the three copy/move constructors.

Now, according to g++, I am only allowed to specify "=default" inside
the class for 2 of them, for the one that takes an lvalue reference I
can only do it outside the class (though I can add "inline" if I like),
otherwise I get:

error: ‘A::A(A&)’ declared to take non-const reference cannot be
defaulted in the class body

Now I am quite confused as to why that is...



#include <iostream>
struct A {
A()=default;
template<class...U> A(U&&...){
std::cout << "NOOOOO!\n";
}
A(A&);
A(A const&)=default;
A(A &&)=default;
};
A::A(A&)=default;
int main(){
A a;
std::cout << "---\n";
A b{a};
std::cout << "---\n";
A c{(A&)a}; // same as the previous one
std::cout << "---\n";
A d{(A const&)a};
std::cout << "---\n";
A e{(A&&)a};
}
 
Ad

Advertisements

J

Johannes Schaub (litb)

Marc said:
See below for the code. A variadic argument U&&... matches a
copy-from-lvalue better than the implicit copy constructors, so I need
to declare a copy constructor from lvalue. And since its existence
prevents the implicit declaration of the other two versions (from const
ref and from rvalue), I need to declare those 2 as well. Now I don't
want anything fancy for those and I am fine with whatever the compiler
can generate by default, so I default the three copy/move constructors.

Note that you can employ a trick here. Your template will be a better match
than the copy constructor only for U = "A&" and "A const". For that, you can
make the class declaration not declare such a constructor, but introduce it
later on immediately.

struct A {
A()=default;
template<class...U> A(U&&...){
std::cout << "NOOOOO!\n";
}
// note: non-template functions are preferred!
inline A(A&, int); // does not prevent implicit A(A const&);
inline A(A const&&, int); // does not prevent A(A&&);
};
A::A(A&, int = 0) {
/* copy */
}
A::A(A const&&, int = 0) {
/* copy */
}

This is more work than necessary, though, and I haven't tested it.
Note that this way too, your class becomes non-trivial (it's trivial after
the class was defined, but becomes non-trivial after the copy constructors
are defined later on).

You could use SFINAE for this, although I admit it is ugly to catch the
exact case when U... has sizeof...(U) == 1 and such. The following should
suffice, I believe (insert typename as necessary... - and I haven't tested
it)

struct A {
A()=default;
template<class U,
class = enable_if<
!is_same<remove_reference<U>::type const, A const>::value>
::type>
A(U&&) {
std::cout << "YEEEES!\n";
}

template<class...U, class = bool[sizeof...(U) > 1]>
A(U&&...){
std::cout << "YEEEES!\n";
}
};

This way, your class will still be trivially copyable - as opposed to the
out-of-line defaulting workaround.

Note that if issue report http://www.open-
std.org/jtc1/sc22/wg21/docs/cwg_active.html#1080 is rejected, then you don't
need any precautions: A(U&&) will never be used then inplace of a move/copy
constructor. But I doubt it.

The reason GCC rejects your code apparently has something to do with that
the Standard marks first-declaration-explicitly-defaulted functions
potentially trivial (in your case, it actually is trivial). The Standard
requires that such an explicit-defaulting must match the parameter types
that the implicitly-declared copy constructor/move constructor would have.
 
M

Marc

Hello, thank you for your answer.
Note that you can employ a trick here. Your template will be a better match
than the copy constructor only for U = "A&" and "A const". For that, you can
make the class declaration not declare such a constructor, but introduce it
later on immediately.

struct A {
A()=default;
template<class...U> A(U&&...){
std::cout << "NOOOOO!\n";
}
// note: non-template functions are preferred!
inline A(A&, int); // does not prevent implicit A(A const&);
inline A(A const&&, int); // does not prevent A(A&&);
};
A::A(A&, int = 0) {
/* copy */
}
A::A(A const&&, int = 0) {
/* copy */
}

Really, that's legal? (yes, g++ accepts it)
You could use SFINAE for this

Yes, I know and may end up using it, it just seemed like a case for
simple code (I feel like I meta-program too much already).
, although I admit it is ugly to catch the
exact case when U... has sizeof...(U) == 1 and such. The following should
suffice, I believe

Maybe is_same<tuple<remove_ref<U>::type const volatile...>,tuple<A const
volatile>> etc?
But the idea is the same.
(btw, I had forgotten about the array-of-size-0 trick, thanks for
reminding me)
(insert typename as necessary...

:)
I still can't get my fingers to type typename without the compiler
complaining first...
This way, your class will still be trivially copyable - as opposed to the
out-of-line defaulting workaround.

Oh, I hadn't thought about that... Indeed it would be wrong to lose this
property.
Note that if issue report http://www.open-
std.org/jtc1/sc22/wg21/docs/cwg_active.html#1080 is rejected, then you don't
need any precautions: A(U&&) will never be used then inplace of a move/copy
constructor. But I doubt it.

I always found it strange that U&& would be used for copying lvalues but
not rvalues. If there was a good way to make this inconsistancy
disappear...
The reason GCC rejects your code apparently has something to do with that
the Standard marks first-declaration-explicitly-defaulted functions
potentially trivial (in your case, it actually is trivial). The Standard
requires that such an explicit-defaulting must match the parameter types
that the implicitly-declared copy constructor/move constructor would have.

And since A(A&) would not be implicitly declared, I can't default it in
the body. And if I changed A to be in the case where the one implicitly
generated is A(A&), I guess I couldn't default A(A const&) then. It
still seems like a "bug".
 
Ad

Advertisements


Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top