c++templateslanguage-lawyerfriendtemplate-instantiation

Instantiation of friend function defined inside a template


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


Solution

  • [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...