Consider this case:
template<typename T>
struct A {
A(A ???&) = default;
A(A&&) { /* ... */ }
T t;
};
I explicitly declared a move constructor, so I need to explicitly declare a copy constructor if I want to have a non-deleted copy constructor. If I want to default
it, how can I find out the correct parameter type?
A(A const&) = default; // or
A(A &) = default; // ?
I'm also interested in whether you encountered a case where such a scenario actually popped up in real programs. The spec says
A function that is explicitly defaulted shall ...
- have the same declared function type (except for possibly differing ref-qualifiers and except that in the case of a copy constructor or copy assignment operator, the parameter type may be "reference to non-const T", where T is the name of the member function’s class) as if it had been implicitly declared,
If the implicitly-declared copy constructor would have type A &
, I want to have my copy constructor be explicitly defaulted with parameter type A &
. But if the implicitly-declared copy constructor would have parameter type A const&
, I do not want to have my explicitly defaulted copy constructor have parameter type A &
, because that would forbid copying from const lvalues.
I cannot declare both versions, because that would violate the above rule for the case when the implicitly declared function would have parameter type A &
and my explicitly defaulted declaration has parameter type A const&
. From what I can see, a difference is only allowed when the implicit declaration would be A const&
, and the explicit declaration would be A &
.
Edit: In fact, the spec says even
If a function is explicitly defaulted on its first dec- laration, ...
- in the case of a copy constructor, move constructor, copy assignment operator, or move assignment operator, it shall have the same parameter type as if it had been implicitly declared.
So I need to define these out-of-class (which I think doesn't hurt, since as far as I can see the only difference is that the function will become non-trivial, which is likely anyway in those cases)
template<typename T>
struct A {
A(A &);
A(A const&);
A(A&&) { /* ... */ }
T t;
};
// valid!?
template<typename T> A<T>::A(A const&) = default;
template<typename T> A<T>::A(A &) = default;
Alright I found that it is invalid if the explicitly declared function is A const&
, while the implicit declaration would be A &
:
A user-provided explicitly-defaulted function (i.e., explicitly defaulted after its first declaration) is defined at the point where it is explicitly defaulted; if such a function is implicitly defined as deleted, the program is ill-formed.
This matches what GCC is doing. Now, how can I achieve my original goal of matching the type of the implicitly declared constructor?
It seems to me that you would need some type deduction (concepts ?).
The compiler will generally use the A(A const&)
version, unless it is required by one of the member that it is written A(A&)
. Therefore, we could wrap some little template hackery to check which version of a copy constructor each of the member has.
Latest
Consult it at ideone, or read the errors by Clang after the code snippet.
#include <memory>
#include <type_traits>
template <bool Value, typename C>
struct CopyConstructorImpl { typedef C const& type; };
template <typename C>
struct CopyConstructorImpl<false,C> { typedef C& type; };
template <typename C, typename T>
struct CopyConstructor {
typedef typename CopyConstructorImpl<std::is_constructible<T, T const&>::value, C>::type type;
};
// Usage
template <typename T>
struct Copyable {
typedef typename CopyConstructor<Copyable<T>, T>::type CopyType;
Copyable(): t() {}
Copyable(CopyType) = default;
T t;
};
int main() {
{
typedef Copyable<std::auto_ptr<int>> C;
C a; C const b;
C c(a); (void)c;
C d(b); (void)d; // 32
}
{
typedef Copyable<int> C;
C a; C const b;
C c(a); (void)c;
C d(b); (void)d;
}
}
Which gives:
6167745.cpp:32:11: error: no matching constructor for initialization of 'C' (aka 'Copyable<std::auto_ptr<int> >')
C d(b); (void)d;
^ ~
6167745.cpp:22:7: note: candidate constructor not viable: 1st argument ('const C' (aka 'const Copyable<std::auto_ptr<int> >')) would lose const qualifier
Copyable(CopyType) = default;
^
6167745.cpp:20:7: note: candidate constructor not viable: requires 0 arguments, but 1 was provided
Copyable(): t() {}
^
1 error generated.
Before Edition
Here is the best I could come up with:
#include <memory>
#include <type_traits>
// Usage
template <typename T>
struct Copyable
{
static bool constexpr CopyByConstRef = std::is_constructible<T, T const&>::value;
static bool constexpr CopyByRef = !CopyByConstRef && std::is_constructible<T, T&>::value;
Copyable(): t() {}
Copyable(Copyable& rhs, typename std::enable_if<CopyByRef>::type* = 0): t(rhs.t) {}
Copyable(Copyable const& rhs, typename std::enable_if<CopyByConstRef>::type* = 0): t(rhs.t) {}
T t;
};
int main() {
{
typedef Copyable<std::auto_ptr<int>> C; // 21
C a; C const b; // 22
C c(a); (void)c; // 23
C d(b); (void)d; // 24
}
{
typedef Copyable<int> C; // 27
C a; C const b; // 28
C c(a); (void)c; // 29
C d(b); (void)d; // 30
}
}
Which almost works... except that I got some errors when building the "a".
6167745.cpp:14:78: error: no type named 'type' in 'std::enable_if<false, void>'
Copyable(Copyable const& rhs, typename std::enable_if<CopyByConstRef>::type* = 0): t(rhs.t) {}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~
6167745.cpp:22:11: note: in instantiation of template class 'Copyable<std::auto_ptr<int> >' requested here
C a; C const b;
^
And:
6167745.cpp:13:67: error: no type named 'type' in 'std::enable_if<false, void>'
Copyable(Copyable& rhs, typename std::enable_if<CopyByRef>::type* = 0): t(rhs.t) {}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~
6167745.cpp:28:11: note: in instantiation of template class 'Copyable<int>' requested here
C a; C const b;
^
Both occurs for the same reason, and I do not understand why. It seems that the compiler tries to implement all constructors even though I have a default constructor. I would have thought that SFINAE would apply, but it seems it does not.
However, the error line 24 is correctly detected:
6167745.cpp:24:11: error: no matching constructor for initialization of 'C' (aka 'Copyable<std::auto_ptr<int> >')
C d(b); (void)d;
^ ~
6167745.cpp:13:7: note: candidate constructor not viable: 1st argument ('const C' (aka 'const Copyable<std::auto_ptr<int> >')) would lose const qualifier
Copyable(Copyable& rhs, typename std::enable_if<CopyByRef>::type* = 0): t(rhs.t) {}
^
6167745.cpp:11:7: note: candidate constructor not viable: requires 0 arguments, but 1 was provided
Copyable(): t() {}
^
Where we can see that the CopyByConstRef was correctly evicted from the overload set, hopefully thanks to SFINAE.