mercredi 30 décembre 2015

Why are std::vector and std::string's comparison operators defined as template functions?

A little overview. I'm writing a class template that provides a strong typedef; by strong typedef I am contrasting with a regular typedef which just declares an alias. To give an idea:

using EmployeeId = StrongTypedef<int>;

Now, there are different schools of thought on strong typedefs and implicit conversions. One of these schools says: not every integer is an EmployeeId, but every EmployeeId is an integer, so you should allow implicit conversions from EmployeeId to integer. And you can implement this, and write things like:

EmployeeId x(4);
assert(x == 4);

This works because x gets implicitly converted to an integer, and then integer equality comparison is used. So far, so good. Now, I want to do this with a vector of integers:

using EmployeeScores = StrongTypedef<std::vector<int>>;

So I can do things like this:

std::vector<int> v1{1,2};
EmployeeScores e(v1);
std::vector<int> v2(e); // implicit conversion
assert(v1 == v2);

But I still can't do this:

assert(v1 == e);

The reason this doesn't work is because of how std::vector defines its equality check, basically (modulo standardese):

template <class T, class A>
bool operator==(const vector<T,A> & v1, const vector<T,A> & v2) {
...
}

This is a function template; because it gets discarded in an earlier phase of lookup it will not allow a type that converts implicitly to vector to be compared.

A different way to define equality would be like this:

template <class T, class A = std::allocator<T>>
class vector {
... // body

  friend bool operator==(const vector & v1, const vector & v2) {
  ...
}

} // end of class vector

In this second case, the equality operator is not a function template, it's just a regular function that's generated along with the class, similar to a member function. This is an unusual case enabled by the friend keyword.

The question (sorry the background was so long), is why doesn't std::vector use the second form instead of the first? This makes vector behave more like primitive types, and as you can clearly see it helps with my use case. This behavior is even more surprising with string since it's easy to forget that string is just a typedef of a class template.

Two things I've considered: first, some may think that because the friend function gets generated with the class, this will cause a hard failure if the contained type of the vector does not support equality comparison. This is not the case; like member functions of template classes, they aren't generated if unused. Second, in the more general case, free functions have the advantage that they don't need to be defined in the same header as the class, which can have advantages. But this clearly isn't utilized here.

So, what gives? Is there a good reason for this, or was it just a sub-optimal choice?

Aucun commentaire:

Enregistrer un commentaire