Reputation: 558
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
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
Reputation: 172994
why is the
<T>
present after==
? Clearly the<T>
followingoperator==
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
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 writefriend 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