Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to define a friend function declared in a non template class internal to a template class outside of both classes?

I found "how to define a friend template function of a template class outside of its declaration" (SO/cppreference), but how to do that if we add another internal non template class in the mix?

I.e. how to (externally) define operator<< declared in class Internal from the following example:

#include <iostream>

template <typename T>
class External {
public:
    explicit External(T initial) : value{initial} {}
    class Internal {
    public:
        Internal(const External& e) : internal_value{e.value} {}

    private:        
        friend std::ostream& operator<<(std::ostream& os, const Internal& i);
        // ^^^ this one
        /* body
        {
            return os << i.internal_value;
        }
        */

        T internal_value;
    };

    friend std::ostream& operator<<(std::ostream& os, const External& e)
    {
        return os << Internal{e};
    }
private:
    T value;
};

int main()
{
    std::cout << External<int>{5};
}
like image 258
Dev Null Avatar asked Sep 14 '17 05:09

Dev Null


1 Answers

Here's the issue. Despite the fact that External is a template and Internal is a dependent type. The friend function is not templated itself. Odd as it may seem, it doesn't depend on the template parameter.

When you define it inline, then each specialization of the template creates the associated friend function as well. But when it's not inline you need to provide the definition of the operator for each specialization explicitly. And you can't do that with a template, since the function is not a template.

So if you add this after the template declaration:

std::ostream& operator<<(std::ostream& os, External<int>::Internal const& i)
{
    return os << i.internal_value;
}

It will build. And as you can see, only concrete types are being used in the function.

Obviously that isn't much of a solution. Any sensible person would want specializations of External to generate the friend definition as well. The way to accomplish that in a maintainable fashion is to keep the operator definition inline, but instead of doing the work there, delegate to a member function (which is dependent on the template parameter):

class Internal {
public:
    Internal(const External& e) : internal_value{e.value} {}

private:
    std::ostream& print(std::ostream&) const;
    friend std::ostream& operator<<(std::ostream& os, Internal const& i)
    {
        return i.print(os); // Short and sweet on account of being inline
    }

    T internal_value;
};

//....

template<typename T>
std::ostream& External<T>::Internal::print(std::ostream& os) const {
   // Provided outside of the class definition, can be as verbose as you like
   return os << internal_value;
}
like image 172
StoryTeller - Unslander Monica Avatar answered Sep 28 '22 10:09

StoryTeller - Unslander Monica