Solution
Let's analyze the solution above and see how
well it measures up to what the question asked. Recall that the
original question was: How can you best implement copy construction
and copy assignment for the following fixed-length vector class?
How can you provide maximum usability for construction and
assignment? Hint: Think about the kinds of things that client code
might want to do.
Copy Construction and Copy
Assignment
First, note that the first question is a red
herring. Did you spot it? The original code already had a copy
constructor and a copy assignment operator that worked just fine,
thank you very much. Our solution proposes to address the second
question by adding a templated constructor and a templated
assignment operator to make construction and assignment more
flexible.
template<typename O, size_t osize>
fixed_vector( const fixed_vector<O,osize>& other )
{
copy( other.begin(),
other.begin()+min(size,osize),
begin() );
}
template<typename O, size_t osize>
fixed_vector<T,size>&
operator=( const fixed_vector<O,osize>& other )
{
copy( other.begin(),
other.begin()+min(size,osize),
begin() );
return *this;
}
Note that the above functions are not a copy constructor and a copy assignment
operator. Here's why: A copy constructor or copy assignment
operator specifically constructs/assigns from another object of
exactly the same type—including the same template arguments, if the
class is templated. For example:
struct X
{
template<typename T>
X( const T& ); // NOT copy constructor, T can't be X
template<typename T>
operator=( const T& );
// NOT copy assignment, T can't be X
};
"But," you say, "those two templated member
functions could exactly match the signatures of copy construction
and copy assignment!" Well, actually, no—they couldn't, because in
both cases, T may not be X. To quote from the
standard (12.8/2, note 4):
Because a
template constructor is never a copy constructor, the presence of such a template does not suppress the
implicit declaration of a copy constructor. Template constructors
participate in overload resolution with other constructors,
including copy constructors, and a template constructor may be used
to copy an object if it provides a better match than other
constructors.
There's similar wording for the copy assignment
operator (in 12.8/9 note 7). So the proposed solution, in fact,
still has the same copy constructor and copy assignment operator as
the original code, because the compiler still generates the
implicit versions. What we've done is extend the construction and
assignment flexibility, not replace the old versions.
For another example, consider the following
program:
fixed_vector<char,4> v;
fixed_vector<int,4> w;
fixed_vector<int,4> w2(w);
// calls implicit copy constructor
fixed_vector<int,4> w3(v);
// calls templated conversion constructor
w = w2; // calls implicit copy assignment operator
w = v; // calls templated assignment operator
So the question really wanted us to provide
flexible "construction and assignment from other
fixed_vectors," not specifically flexible "copy
construction and copy assignment," which already existed.
Usability Issues for Construction and
Assignment
There are two major usability
considerations.
-
Support varying
types (including inheritance).
While fixed_vector definitely is and
should remain a homogeneous container, sometimes it makes sense to
construct or assign from another fixed_vector that
actually contains different objects. As long as the source objects
are assignable to our type of object, this should be allowed. For
example, clients may want to write something like this:
fixed_vector<char,4> v;
fixed_vector<int,4> w(v); // templated construction
w = v; // templated assignment
class B { /*...*/ };
class D : public B { /*...*/ };
fixed_vector<D*,4> x;
fixed_vector<B*,4> y(x); // templated construction
y = x; // templated assignment
This is legal and works as expected because a
D* can be assigned to a B*.
-
Support varying
sizes.
Similarly, clients may want to construct or
assign from fixed_vectors with different sizes. Again, it
makes sense to support this feature. For example:
fixed_vector<char,6> v;
fixed_vector<int,4> w(v); // initializes using 4 values
w = v; // assigns using 4 values
class B { /*...*/ };
class D : public B { /*...*/ };
fixed_vector<D*,16> x;
fixed_vector<B*,42> y(x); // initializes using 16 values
y = x; // assigns using 16 values
|