ivme
ivme

Reputation: 558

Friend comparison and relational operators in C++ class template

From Lippman et al C++Primer 5th edition, section 16.1.2:

//forward declarations needed for friend declarations in Blob
template <typename> class BlobPtr;
template <typename> class Blob;
template <typename T> bool operator==(const Blob<T>&, const Blob<T>&)

template <typename T> class Blob {
   friend class BlobPtr<T>;
   friend bool operator==<T>(const Blob<T>&, const Blob<T>&);
}

First question: in the line

friend bool operator==<T>(const Blob<T>&, const Blob<T>&);

why is the <T> present after ==? Why not simply write

friend bool operator==(const Blob<T>&, const Blob<T>&);

I added the following code to define operator== and to instantiate the class template. It successfully compiles and links:

template <typename T>
bool operator==(const Blob<T> &lhs, const Blob<T> &rhs) {return true;}

int main() {
    Blob<int> a, b;
    a == b;
}

If I remove the <T> following operator== in the friend declaration, I get a linker error:

Undefined symbols for architecture x86_64: "operator==(Blob<int> const&, Blob<int> const&)", referenced from: _main in partial_blob-3ccda9.o

Clearly the <T> following operator== is necessary, but why?

Second question: If I want to define the relational less than operator < for the same class, I would guess that I should follow the pattern that worked for ==:

1) forward-declare the operator

2) declare the operator as a friend, inserting the additional <T> whose function I don't understand

3) define the operator out-of-class.

I therefore add the following code:

template <typename T> bool operator<(const Blob<T>&, const Blob<T>&);
template <typename T> class Blob {
   //other members as before
   friend bool operator<<T>(const Blob<T>&, const Blob<T>&);
}
bool operator<(const Blob<T>&, const Blob<T>&) {return true;}
int main() {
   //other statements as before
   a < b;
}

This produces a compilation error around operator<<T>, I think because the compiler interprets << as the insertion operator. But if I rewrite the friend declaration as

friend bool operator<(const Blob<T>&, const Blob<T>&);

then I get a linker error similar to the earlier linker error with ==:

"operator<(Blob<int> const&, Blob<int> const&)", referenced from: _main in partial_blob-a85d5d.o

How can I successfully define operator < for this class?

(Note: the operators must be declared as friends because more fully-realized implementations rely on private variables.)

Upvotes: 4

Views: 1755

Answers (3)

ivme
ivme

Reputation: 558

I am posting my own answer, acknowledging Joachim Pileborg and songyuanyao for direction.

I have simplified the code to focus on Question 1 only. Pileborg and Holt correctly pointed out that overloading < merely requires a space to help the compiler parse.

template <typename> class Blob;
template <typename T> bool operator==(const Blob<T>&, const Blob<T>&); //line 2

template <typename T> class Blob {
   friend bool operator==(const Blob<T>&, const Blob<T>&); //line 5
};

template <typename T>
bool operator==(const Blob<T> &lhs, const Blob<T> &rhs) {return true;} //line 9

int main() {
    Blob<int> a, b; //line 12
    a == b; //line 13
}

This code produces an error at link time. To understand why, we’ll look at relevant language from the standard.

From the C++14 Standard n4296, 14.5.4 (See bottom for summary of terminology used here).

For a friend function declaration that is not a template declaration:

(1.1) — if the name of the friend is a qualified or unqualified template-id, the friend declaration refers to a specialization of a function template, otherwise,

(1.2) — if the name of the friend is a qualified-id and a matching non-template function is found in the specified class or namespace, the friend declaration refers to that function, otherwise,

(1.3) — if the name of the friend is a qualified-id and a matching function template is found in the specified class or namespace, the friend declaration refers to the deduced specialization of that function template (14.8.2.6), otherwise,

(1.4) — the name shall be an unqualified-id that declares (or redeclares) a non-template function.

Now we look at the friend declaration on line 5, determining what it refers to according to the four steps listed above.

(1.1) == is not a template-id; move on...

(1.2) == is not a qualified-id; move on...

(1.3) == is not a qualified-id; move on...

(1.4) therefore, == is an unqualified-id that declares (or redeclares) a non-template function.

According to section 7.3.3 of the standard, the friend == is declared in the innermost enclosing namespace -- in this case, the global namespace.

When we instantiate Blob<int> on line 12, the compiler generates source code by substituting int for all occurrences of T in class Blob. The friend declaration in the compiler-generated code then reads:

friend bool operator==(const Blob<int>&, const Blob<int>&);

Thus we have declared a (non-template) overload of operator== in the global namespace, with parameters of type const Blob<int>&.

When a == b is called on line 12, the compiler begins the overload resolution process. It first looks for any non-template overloads that match the argument types. It finds a perfect match in the form of the operator== declared when Blob<int> was instantiated. The linker then looks for the definition of operator== corresponding to the best-match declaration, but it finds none, because the operator==(const Blob<int>&, const Blob<int>&) was never actually defined!

The solution is to use a template-id (which is NOT a template declaration) as the name in the friend declaration:

friend bool operator== <>(const Blob<T>&, const Blob<T>&)

or

friend bool operator== <T>(const Blob<T>&, const Blob<T>&)

Both operator== <> and operator== <T> are template-id’s (see terminology below); the former has an implicit template parameter list deduced from the function parameter list, and the latter has an explicit template parameter list.

When Blob<int> is instantiated on line 12, the compiler generates the following code for the friend declaration:

friend bool operator== <>(const Blob<int>&, const Blob<int>&)

or

friend bool operator== <int>(const Blob<int>&, const Blob<int>&)

In either case, the name of the friend is an unqualified template-id, so by (1.1) above, the friend declaration refers to a specialization of a function template. The compiler then finds then finds a best template match for the requested <int> specialization. The only template it finds is the template declared in line 2 and defined in line 9, which it calls, as desired.

Terminology

qualified-id: an identifier with an attached scoping operator, e.g. std::string or ::i

unqualified-id: an identifier without an attached scoping operator, e.g. string or i.

template-id: the following excerpt (From the C++14 Standard n4296, 14.2) summarizes the structure of template-id’s:

simple-template-id:

 template-name < template-argument-list (opt)>

template-id:

 simple-template-id

 operator-function-id < template-argument-listopt >

 literal-operator-id < template-argument-listopt>

template-name:

 identifier

So some template-id’s would include Foo<T> and ==<T>. (== is an operator-function-id). Notice that, unlike in a template declaration, template <> is not included in a template-id expression.

Upvotes: 0

songyuanyao
songyuanyao

Reputation: 172994

why is the <T> present after ==? Clearly the <T> following operator== is necessary, but why?

Because operator== in friend declaration refers to the function template, you have to specify it explicity. Otherwise a non-template function will be declared, but the definition of it can't be found later. It's not the same sceen as invoking (and instantiation) of function template.

Note T could be omitted but <> is still needed. Such as:

// refers to a full specialization of operator==
friend bool operator== <>(const Blob<T>&, const Blob<T>&);

Another candidate way is to define the operator inside the class declaration, which will be inline and could be declared as non-template function. Such as:

template <typename T> class Blob {
   ...
   friend bool operator==(const Blob&, const Blob&) { 
       return ...;
   }
}

This produces a compilation error around operator<<T>

Yes as you said, it should be written as friend bool operator< <T>(...), or friend bool operator< <>(...), or see my suggestion about non-template function friend.

Upvotes: 3

Holt
Holt

Reputation: 37641

First question: in the line

friend bool operator==<T>(const Blob<T>&, const Blob<T>&);

why is the <T> present after ==? Why not simply write

friend bool operator==(const Blob<T>&, const Blob<T>&);

If you remove the <T>, gcc gives a warning:

warning: friend declaration 'bool operator==(const Blob< <template-parameter-1-1> >&, const Blob< <template-parameter-1-1> >&)' declares a non-template function [-Wnon-template-friend]

Your are making a non-templated function a friend of your class, so the compiler/linker will be looking for a non-templated function, in your case:

bool operator==(const Blob<int>&, const Blob<int>&);

...which does not exist, thus the linker cannot find it.

If you do not add <T> (or <> to the friend declaration), you have to define a function for each type, which is probably not what you want.

Second question: If I want to define the relational less than operator < for the same class, I would guess that I should follow the pattern that worked for ==:

This is a simple problem with the way C++ code is parsed, you need to insert a space between operator< and <<. This is the same problem that existed prior to C++11 where you had to use vector<vector<int> > instead of vector<vector<int>> because of the >>.

Upvotes: 0

Related Questions