Reputation: 51
I was looking into creating a constructor to initialize an encapsulated std::array and came across a problem that the constructor of a copyable type (class A) could not be called explicitly. There are two cases in the given code. A simple example: an object of class B cannot explicitly call the copy constructor for a field of class A. And a complex one: the initialization of each element of the std::array requires an implicit call to the copy constructor. How can I avoid implicitly calling a copy constructor to initialize class members?
#include <cstddef> // std::size_t
#include <utility> // std::forward
#include <array> // std::array
struct A {
explicit A() = default;
explicit A(A&) = default; // ERROR: used implicitly when initializing 'b'
};
struct B {
//explicit B(A& a) : a_{a} {} // partially solves the problem, but cannot be defaulted
A a_;
};
template<class T, std::size_t N>
struct C {
template<class... Ts>
explicit C(Ts&&... il) : values_{std::forward<Ts>(il)...} {} // how to use 'explicit A(A&)'?
std::array<T, N> values_;
};
int main() {
A a;
B b{a}; // basic problem
C<A, 3> c{a, a, a}; // extended problem
return 0;
}
Upvotes: 0
Views: 163
Reputation: 16853
You can avoid implicitly calling a copy constructor by explicitly calling it. More commonly, this would be called explicitly making a copy, as in using A{a}
to explicitly create a (temporary) copy of a
.
B b{A{a}}; // basic problem
// ^^ ^
For the template, I'll add a warning!
You can use the same technique when constructing an array, but first consider if you should. Adding generic code to handle a special case (when T
is A
) can have unforeseen consequences (when T
is not A
). Before modifying the constructor of C<>
, you should find out why the copy constructor of A
is explicit
. One possibility is that copying A
is supported, but expensive, hence each copy needs to be explicitly approved. Is this a valid place to approve copying A
? Is it a valid place to approve copying any type with an explicit copy constructor?
Rather than modifying the template, it might be better to give A
a (non-explicit) move constructor, which is another way to address an expensive-to-copy type. With a move constructor, you could apply the technique inside the main
function, where you know you are dealing with A
objects (and not some other type that should not be implicitly copied).
struct A {
explicit A() = default;
explicit A(A&) = default;
A(A&&) = default; // <--- Added a move constructor
};
C<A, 3> c{A{a}, A{a}, A{a}}; // extended problem
// ^^ ^ ^^ ^ ^^ ^
Still, for completeness, if you are dead-set on making the template work, the technique can be applied there (no need to make explicit copies in main
when initializing c
).
// I don't recommend this, but it does compile:
explicit C(Ts&&... il) : values_{T{std::forward<Ts>(il)}...} {} // how to use 'explicit A(A&)'?
// ^^ ^
Isn't there still a copy from the temporary? Not as of C++17. Explicitly making a copy results in a prvalue, and a prvalue is not yet a materialized object. So when your A
objects are initialized from these prvalues, the prvalues are materialized directly in the A
objects; there is no additional copy. It's as if the prvalue tells the compiler how to construct an object, while the variable name tells the compiler where to construct it. In B b{A{a}}
, the prvalue is A{a}
, which explicitly says to make a copy, while the b
names the place where the copy is to be made.
This is the same mechanism that gave us guaranteed copy elision (when returning a temporary object from a function).
Upvotes: 0
Reputation: 96810
You can use a pointer to do this:
struct B {
std::unique_ptr<A> a_;
};
template<class T, std::size_t N>
struct C {
template<class... Ts>
explicit C(Ts&&... il) : values_{std::make_unique<A>(std::forward<Ts>(il))...} {}
std::array<std::unique_ptr<T>, N> values_;
};
int main() {
A a;
B b{std::make_unique<A>(a)};
C<A, 3> c{a, a, a};
}
Upvotes: -1