Instantiation of friend function defined inside a template

391 views Asked by At

This is a follow up of this question. The original case was something else, but in the course of me writing a poor answer and OP clarifying, it turned out that we probably need the help of a language-lawyer to understand what is going on.

In Thinking in C++ - Practical Programming Vol 2 one can find the following example (intendation mine, online here):

//: C05:FriendScope3.cpp {-bor}
// Microsoft: use the -Za (ANSI-compliant) option
#include <iostream>
using namespace std;
 
template<class T> class Friendly {
    T t;
public:
    Friendly(const T& theT) : t(theT) {}
    friend void f(const Friendly<T>& fo) {
        cout << fo.t << endl;
    }
    void g() { f(*this); }
};
 
void h() {
    f(Friendly<int>(1));
}
 
int main() {
    h();
    Friendly<int>(2).g();
} ///:~

They continue to explain (emphasize mine):

There is an important difference between this and the previous example: f is not a template here, but is an ordinary function. (Remember that angle brackets were necessary before to imply that f( ) was a template.) Every time the Friendly class template is instantiated, a new, ordinary function overload is created that takes an argument of the current Friendly specialization. This is what Dan Saks has called making new friends. [68] This is the most convenient way to define friend functions for templates.

So far so good. The puzzling part is "f is not a template here, but is an ordinary function" + "Every time the Friendly class template is instantiated, a new, ordinary function overload is created" when you consider this example:

template <typename T>
struct foo {
    friend void bar(foo x){
        x = "123";
    }
};

int main() {
    foo<int> x;
    bar(x);
}

Instantiating foo<int> does not cause a compiler error! Only calling bar(x) causes (gcc 10.2):

<source>: In instantiation of 'void bar(foo<int>)':
<source>:10:10:   required from here
<source>:4:11: error: no match for 'operator=' (operand types are 'foo<int>' and 'const char [4]')
    4 |         x = "123";
      |         ~~^~~~~~~
<source>:2:8: note: candidate: 'constexpr foo<int>& foo<int>::operator=(const foo<int>&)'
    2 | struct foo {
      |        ^~~
<source>:2:8: note:   no known conversion for argument 1 from 'const char [4]' to 'const foo<int>&'
<source>:2:8: note: candidate: 'constexpr foo<int>& foo<int>::operator=(foo<int>&&)'
<source>:2:8: note:   no known conversion for argument 1 from 'const char [4]' to 'foo<int>&&'

Instantiation of an ordinary function? That only fails when the function is called? What is going on here?

Is bar really an ordinary function? It is only instantiated when called? Why, when it is an ordinary function? What is actually happening with respect to bar when foo<int> is instantiated (the authors call it "a new, ordinary function overload is created", not sure what that is supposed to mean)?

Sorry for the many ?s, its just too puzzling. And please don't miss the language-lawyer tag, I want to know the why / what parts of the standard make it so, not just the what.

PS: Just to be sure I checked again and the three usual suspects all compile the example without major complaints when bar is not called: https://godbolt.org/z/Wcsbc5qjv

2

There are 2 answers

2
Igor Tandetnik On BEST ANSWER

[temp.inst]/2 The implicit instantiation of a class template specialization causes the implicit instantiation of the declarations, but not of the definitions, default arguments, or noexcept-specifiers of the class ... friends...

[temp.inst]/4 ... A function whose declaration was instantiated from a friend function definition is implicitly instantiated when it is referenced in a context that requires a function definition to exist...

0
Davis Herring On

Constructs like this that are part of a template but not themselves templates are called templated because they are nonetheless subject to many of the same rules (especially where methods and friends of a class template are instantiated separately, giving each its own “instantiation status”). The standard itself has slowly been using more precise language for this situation, partly spurred on by constexpr-if which introduced statements that are templated (since they must be instantiated separately, to allow doing so for only one branch) even though there are no statement templates. (An older term for these constructs that might be useful for further research is “temploids”.)