3

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

463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185
  • 1
    AFAIK, the definition of functions declared inside a class template (friends are no exception) is only really instantiated when they are ODR-used, otherwise only the declarations are instantiated. That's why you could instantiate a `std::map` with a non-default-constructible type as long as you do not use its `operator[]`. – ph3rin Apr 28 '21 at 21:07
  • @Meowmere sounds reasonable, though then this should compile without error as well (difference is that now `bar` does not depend on `T`) https://godbolt.org/z/b8nq5abWq, but now clang already errs out on instantiation of `foo`. Well at the same time gcc and msvc accept it, maybe that will be next follow up. Up to now I though I understand the case of `map::operator[]`, its a member of a class template, but here `bar` is no member. – 463035818_is_not_an_ai Apr 28 '21 at 21:18
  • 2
    This [issue](https://wg21.cmeerw.net/cwg/issue2174) seems relevant. The added wording to C++17 suggests that the definition of `bar` is indeed not instantiated until it's needed. BTW, the [example](https://godbolt.org/z/fW1TxaeG9) in your comment has nothing to do with `friend`. It's just that gcc doesn't parse the definitions of member functions of templates until the member is instantiated. I'm not sure if either approach is wrong. – cigien Apr 28 '21 at 21:30
  • 2
    @largest Hmm. There are rules about no valid specialization (by which the standard means instaniation) of a template making a program if;ndr. But if it isn't a template... Then0 again, either is a method of a template. Anyhow, there are under a dozen if;ndr in the standard, take a look for that one, it might apply. – Yakk - Adam Nevraumont Apr 28 '21 at 22:35
  • It sounds like a good example of how concepts can help: https://godbolt.org/z/qK1sxq4Ts – Bob__ Apr 29 '21 at 09:12
  • @Bob__: Issue is not with `T`, but `foo` directly. Even `bar(foo{});` is incorrect. – Jarod42 Apr 29 '21 at 09:22
  • @Jarod42 Now I see, thanks. It would still require some clunky [workaround](https://godbolt.org/z/344Eqsf4b). – Bob__ Apr 29 '21 at 11:09
  • @Bob__: Not sure it is correct: `static_assert` would check property of incomplete type. and as Yakk commented, there might already be ill formed (NDR), so even if not required, diagnositc might be emitted. – Jarod42 Apr 29 '21 at 12:39

2 Answers2

1

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

Igor Tandetnik
  • 50,461
  • 4
  • 56
  • 85
  • now I am convinced that it was colloquial speech when they wrote "a new, ordinary function overload is created". In combination with the other answer, things are much clearer now. In some sense I was writing the question in the name of someone else, I hope their doubts are cleared as well. Can you tell from which version of the standard this is quoted? – 463035818_is_not_an_ai Apr 29 '21 at 17:11
  • 1
    @largest_prime_is_463035818 This is C++17. C++20 has similar language in **[temp.inst]/(3.1)** and **[temp.inst]/5** – Igor Tandetnik Apr 29 '21 at 17:31
1

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”.)

Davis Herring
  • 36,443
  • 4
  • 48
  • 76