Search code examples
c++templatesnamespacesoperatorsfriend

Global scope friend operator declaration when class in namespace and using templated type as return type


I am struggling with friend statement for a templated operator and namespaces. Sorry if I'm a bit long but I want to give a good description of my issue.

First, some context. Forget about the namespace at present. I have a class A and a public operator that needs to access its private member:

template<typename U>
struct B { U valb; };

template<typename U>
struct C { U valc; };

template<typename U,typename V>
struct A
{ 
  private:
    U v1; V v2;

  template<typename T1,typename T2>
  friend A<T1,T2> operator * ( const B<T2>&, const C<T1>& );
};

template<typename T1,typename T2>
A<T1,T2>
operator * ( const B<T2>& b, const C<T1>& c )
{
    A<T1,T2> a;
    a.v1 = c.valc * b.valb; // dummy
    return a;
}

int main()
{
    B<float> b;
    C<int> c;
    auto a = b * c;
}

This builds fine.

Now for some reason I want to put class A in a namespace, (mostly to remove it from the public API, user code will use "sub" types, declared with a using declaration). Now the trouble begins.

I am building on this answer, that covers that topic and works fine. It explains that I need to forward declare the class, then the operator, and in the friend declaration, prefix the operator with ::.

The only difference between the situation described in that linked question and mine is the return type. In my case, it is a templated type. This seems to be the trouble (or is it?)

So I tried that (online here):

template<typename U>
struct B { U valb; };

template<typename U>
struct C { U valc; };

// forward declaration of class A
namespace ns {
template<typename U,typename V> struct A;
}

// forward declaration of operator
template<typename T1,typename T2>
ns::A<T1,T2>
operator * ( const B<T2>&, const C<T1>& );

namespace ns {
  template<typename U,typename V>
  struct A                            // class declaration
  {
    template<typename T1,typename T2>
    friend A<T1,T2> ::operator * ( const B<T2>&, const C<T1>& );

    private:
      U v1; V v2;
  };
} // namespace 

// operator definition
template<typename T1,typename T2>
ns::A<T1,T2> operator * ( const B<T2>& b, const C<T1>& c )
{
    ns::A<T1,T2> a;
    a.v1 = c.valc * b.valb; // dummy
    return a;
}

int main()
{
    B<float> b;
    C<int> c;
    auto a = b * c;
}

This fails to build with:

error: ISO C++ forbids declaration of 'operator*' with no type [-fpermissive]    
   23 |         friend A<T1,T2> ::operator * ( const B<T2>&, const C<T1>& );

And if I remove the ::, then the operator is not recognized as a friend.

What am I doing wrong? How can I manage that issue?


Solution

  • Unless the befriend function template is declared already, I don't think you define this function template outside the class definition: essentially, there is no way to actually name the operator. To make things a bit more interesting, the two parameter types operated on are actually in a different namespace, i.e., the operator*() really needs to be defined in a different namespace than ns. It seems this does the trick:

    template<typename U> struct B { U valb; };
    template<typename U> struct C { U valc; };
    
    // declaration of ns::A to declare the operator*
    namespace ns { template<typename U,typename V> struct A; }
    
    template<typename T1,typename T2>
    ns::A<T1,T2> operator * ( const B<T2>&, const C<T1>& );
    
    namespace ns {
        template<typename U,typename V>
        struct A {
            template<typename T1,typename T2>
            friend auto ::operator * ( const B<T2>&, const C<T1>& ) -> A<T1, T2>;
    
        private:
            U v1; V v2;
        };
    }
    
    template<typename T1,typename T2>
    ns::A<T1,T2> operator * ( const B<T2>& b, const C<T1>& c ) {
        ns::A<T1,T2> a;
        a.v1 = c.valc * b.valb; // dummy
        return a;
    }
    
    int main()
    {
        B<float> b;
        C<int> c;
        auto a = b * c;
    }
    

    For a live demo see this Compiler Explorer link.