Function template instantiation and friend declaration

Viewed 87

I've just started learning templates in C++ and for practice purposes wrote this simple code

#include <iostream>

template<typename T>
class A;

template<typename T>
std::ostream& operator<<(std::ostream& out, A<T> x);

template <typename T>
class A
{
    T m_x = 10;
public:
    A(T x) : m_x{ x } {}
    friend std::ostream& operator<< <T>(std::ostream& out, A x);
};

template<typename T>
std::ostream& operator<<(std::ostream& out, A<T> x)
{
    out << "m_x = " << x.m_x;
    return out;
}


int main()
{
    A<int> a1{ 10 };
    std::cout << a1 << '\n';
}

It works as expected, that is I get 10 as the output, but there is one thing that bothers me. At which point is the operator<< function instantiated? Does it happen at the point of creation of the a1 object (this is also the point where A<int> is implicitly instatiated, right?) or does it happen when I call the operator<< in std::cout << a1 << '\n'? My guess is that the second option is correct and I base it on this excerpt from cppreference which says that:

When code refers to a function in context that requires the function definition to exist, or if the existence of the definition affects the semantics of the program (since C++11), and this particular function has not been explicitly instantiated, implicit instantiation occurs.

But is it true that a friend declaration does not require the function definition to exist?

I'm sorry if this question is ill-pharased, I did my best to use the nomenclature right, but I'm just a beginner.

EDIT

What about this?

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

if I put a friend function definition inside a class, every instantiation of that class causes a new, ordinary function overload to be created that takes an argument of the current specialization, hence I would expect to see an error as soon as I write this: foo<int> x; but I don't get one... (for example, bar(x); causes the error)

2 Answers

I am not a language lawyer, so I'll try to answer with examples.

There are two aspects. First friend declaration.

A friend delcaration is a declaration. For example:

struct foo {
    friend void bar();
};

int main() {
    foo f;
}

Compiles and executes without any problem.

friend void bar(); declares a function bar. This functions is never defined. As long as we do not call it, thats not an issue. Of course, typically you would provide a definition, but if the function is never called you do not necessarily need to define it. Actually there are situations where having a declaration but no definition is on purpose, though thats out of scope of the quesiton.

Next, is templates and the question when member functions are instantiated. For that, consider this example:

#include <iostream>

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

int main() {
    foo<int> x;
    //x.bar();// error!
}

Compiles and executes without any problem.

Initializing an int with the string literal "123" is of course non-sense. Its only to provoke a compiler error when the method is instantiated. We can create an object of foo<int>. Though, we cannot call foo<int>::bar, because its not valid.

You can find many examples of methods of class templates that can only conditionally be instantiated in the standard library. For example std::map<key_t,value_t>::operator[]. It requires value_t to be default constructible, because it potentially has to default construct a value_t. You can use a std::map with a non-default-constructible value_t, you just cannot call its operator[]:

#include <map>

struct foo {
    foo(int){}
};

int main() {
    std::map<int,foo> x;     // completely fine
    x[1] = foo(1);           // error
}

I spare you the complete error message. It is horrible, the essential part is:

 error: no matching function for call to 'foo::foo()'

The overload of the operator<< in your case gives you an individual overload to every template classA<T>, so I can assume, that instantiation of operator<< can be with every instantiation of your class A<T>.

You can checkout this post, it can be useful in your case.

Related